From 003e982ff87c9f7e5b196d7a04a28eb5586023e6 Mon Sep 17 00:00:00 2001 From: baldeau Date: Thu, 14 Nov 2024 10:36:22 +0100 Subject: [PATCH] add some ui improvements --- lib/app/view/app.dart | 7 ++ lib/objectbox-model.json | 7 +- lib/objectbox.g.dart | 19 +++- lib/recordings/view/recordings_details.dart | 100 +++++++++++------- lib/recordings/view/recordings_page.dart | 37 ++++++- .../cubit/xiao_connector_cubit.dart | 21 +++- lib/xiao_connector/models/capture_box.dart | 4 + lib/xiao_connector/models/capture_point.dart | 1 - lib/xiao_connector/view/connected_view.dart | 50 +++++---- lib/xiao_connector/view/failure_view.dart | 2 +- xiao_controller_code/main.go | 1 + 11 files changed, 180 insertions(+), 69 deletions(-) diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index c24481c..7ec55e6 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:forui/forui.dart'; @@ -46,6 +47,12 @@ class _AppState extends State { // adapterState: _adapterState, // ); + // only allow portrait orientations + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + return MaterialApp.router( title: 'Xiao Pet Tracker', localizationsDelegates: const [ diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index 5a674f5..3f23f56 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -69,7 +69,7 @@ }, { "id": "2:1642885530071590702", - "lastPropertyId": "3:4134744801341910087", + "lastPropertyId": "4:5477306218303066701", "name": "CaptureBox", "properties": [ { @@ -87,6 +87,11 @@ "id": "3:4134744801341910087", "name": "uuid", "type": 9 + }, + { + "id": "4:5477306218303066701", + "name": "startTime", + "type": 10 } ], "relations": [] diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart index 637da20..340432f 100644 --- a/lib/objectbox.g.dart +++ b/lib/objectbox.g.dart @@ -87,7 +87,7 @@ final _entities = [ obx_int.ModelEntity( id: const obx_int.IdUid(2, 1642885530071590702), name: 'CaptureBox', - lastPropertyId: const obx_int.IdUid(3, 4134744801341910087), + lastPropertyId: const obx_int.IdUid(4, 5477306218303066701), flags: 0, properties: [ obx_int.ModelProperty( @@ -104,6 +104,11 @@ final _entities = [ id: const obx_int.IdUid(3, 4134744801341910087), name: 'uuid', type: 9, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 5477306218303066701), + name: 'startTime', + type: 10, flags: 0) ], relations: [], @@ -252,10 +257,11 @@ obx_int.ModelDefinition getObjectBoxModel() { objectToFB: (CaptureBox object, fb.Builder fbb) { final typeOffset = fbb.writeString(object.type); final uuidOffset = fbb.writeString(object.uuid); - fbb.startTable(4); + fbb.startTable(5); fbb.addInt64(0, object.id); fbb.addOffset(1, typeOffset); fbb.addOffset(2, uuidOffset); + fbb.addInt64(3, object.startTime.millisecondsSinceEpoch); fbb.finish(fbb.endTable()); return object.id; }, @@ -266,7 +272,10 @@ obx_int.ModelDefinition getObjectBoxModel() { .vTableGet(buffer, rootOffset, 6, ''); final uuidParam = const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 8, ''); - final object = CaptureBox(type: typeParam, uuid: uuidParam) + final startTimeParam = DateTime.fromMillisecondsSinceEpoch( + const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0)); + final object = CaptureBox( + type: typeParam, uuid: uuidParam, startTime: startTimeParam) ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); return object; @@ -336,4 +345,8 @@ class CaptureBox_ { /// See [CaptureBox.uuid]. static final uuid = obx.QueryStringProperty(_entities[1].properties[2]); + + /// See [CaptureBox.startTime]. + static final startTime = + obx.QueryDateProperty(_entities[1].properties[3]); } diff --git a/lib/recordings/view/recordings_details.dart b/lib/recordings/view/recordings_details.dart index b02453c..b50ee81 100644 --- a/lib/recordings/view/recordings_details.dart +++ b/lib/recordings/view/recordings_details.dart @@ -5,10 +5,9 @@ import 'package:csv/csv.dart'; import 'package:ditredi/ditredi.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:ditredi/ditredi.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:vector_math/vector_math_64.dart'; import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart'; import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart'; @@ -31,8 +30,11 @@ class RecordingsDetailsPage extends StatefulWidget { class _RecordingsDetailsPageState extends State { final _controllerRotation = DiTreDiController(); final _controllerAcceleration = DiTreDiController(); + final f = DateFormat('hh:mm:ss.SSS'); List _capturePoints = []; + bool isStoringCSV = false; + @override void initState() { _capturePoints = @@ -54,10 +56,10 @@ class _RecordingsDetailsPageState extends State { height: 16, ), Text( - 'Capture Start Time: ${DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend)}', + 'Start Time: ${f.format(DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend))}', ), Text( - 'Capture End Time: ${DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend)}', + 'End Time: ${f.format(DateTime.fromMillisecondsSinceEpoch(_capturePoints.last.millisecondsSinceEpochSend))}', ), const SizedBox( height: 8, @@ -66,50 +68,68 @@ class _RecordingsDetailsPageState extends State { const SizedBox( height: 16, ), - ElevatedButton( - onPressed: () async { - // await Permission.storage.request(); + if (isStoringCSV) + const CircularProgressIndicator() + else + ElevatedButton( + onPressed: () async { + setState(() { + isStoringCSV = true; + }); + // await Permission.storage.request(); - final Directory? downloadsDir = (Platform.isIOS) - ? await getApplicationDocumentsDirectory() - : await getDownloadsDirectory(); + final Directory? downloadsDir = (Platform.isIOS) + ? await getApplicationDocumentsDirectory() + : await getDownloadsDirectory(); - File f = File( - downloadsDir!.path + "/${widget.type}_${widget.uuid}.csv"); + File f = File(downloadsDir!.path + + "/${widget.type}_${widget.uuid}.csv"); - List> rows = []; + List> rows = []; - List row = []; - row.add('sendTimeStamp'); - row.add('receivedTimeStamp'); - row.add('accelerationX'); - row.add('accelerationY'); - row.add('accelerationZ'); - row.add('rotationX'); - row.add('rotationY'); - row.add('rotationZ'); - - rows.add(row); - - for (var i = 0; i < _capturePoints.length; i++) { List row = []; - row.add(_capturePoints[i].millisecondsSinceEpochSend); - row.add(_capturePoints[i].millisecondsSinceEpochReceived); - row.add(_capturePoints[i].accelerationX); - row.add(_capturePoints[i].accelerationY); - row.add(_capturePoints[i].accelerationZ); - row.add(_capturePoints[i].rotationX); - row.add(_capturePoints[i].rotationY); - row.add(_capturePoints[i].rotationZ); + row.add('sendTimeStamp'); + row.add('receivedTimeStamp'); + row.add('accelerationX'); + row.add('accelerationY'); + row.add('accelerationZ'); + row.add('rotationX'); + row.add('rotationY'); + row.add('rotationZ'); + rows.add(row); - } - String csv = const ListToCsvConverter().convert(rows); + for (var i = 0; i < _capturePoints.length; i++) { + List row = []; + row.add(_capturePoints[i].millisecondsSinceEpochSend); + row.add(_capturePoints[i].millisecondsSinceEpochReceived); + row.add(_capturePoints[i].accelerationX); + row.add(_capturePoints[i].accelerationY); + row.add(_capturePoints[i].accelerationZ); + row.add(_capturePoints[i].rotationX); + row.add(_capturePoints[i].rotationY); + row.add(_capturePoints[i].rotationZ); + rows.add(row); + } - f.writeAsString(csv); - }, - child: const Text('Export to CSV'), - ), + String csv = const ListToCsvConverter().convert(rows); + + f.writeAsString(csv); + setState( + () { + isStoringCSV = false; + }, + ); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Succesfully exported the CSV!'), + ), + ); + } + }, + child: const Text('Export to CSV'), + ), const SizedBox( height: 16, ), diff --git a/lib/recordings/view/recordings_page.dart b/lib/recordings/view/recordings_page.dart index 490f1fa..2eed5b0 100644 --- a/lib/recordings/view/recordings_page.dart +++ b/lib/recordings/view/recordings_page.dart @@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:forui/forui.dart'; +import 'package:intl/intl.dart'; import 'package:xiao_pet_tracker/app_router/app_router.gr.dart'; import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart'; import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.dart'; -import 'package:xiao_pet_tracker/xiao_connector/view/xiao_connector_page.dart'; @RoutePage() class RecordingsPage extends StatelessWidget { @@ -26,6 +26,7 @@ class RecordingsView extends StatefulWidget { class _SettingsViewState extends State { List _captureBoxes = []; + final f = DateFormat('hh:mm - dd.MM.yyyy '); @override void initState() { @@ -47,7 +48,7 @@ class _SettingsViewState extends State { return ListTile( leading: FIcon(FAssets.icons.pawPrint), title: Text('Collection Type: ${_captureBoxes[i].type}'), - subtitle: Text('Uuid: ${_captureBoxes[i].uuid}'), + subtitle: Text(f.format(_captureBoxes[i].startTime)), onTap: () { AutoRouter.of(context).push( RecordingsDetailsRoute( @@ -56,6 +57,38 @@ class _SettingsViewState extends State { ), ); }, + onLongPress: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Delete this recording?'), + content: const Text( + "Are you sure you want to delete this recording with all of it's data? This cannot be undone.", + textAlign: TextAlign.center, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + context + .read() + .deleteRecording(_captureBoxes[i].uuid); + _pullRefresh(); + Navigator.of(context).pop(); + }, + child: const Text('Delete'), + ), + ], + ); + }, + ); + }, ); }, ), diff --git a/lib/xiao_connector/cubit/xiao_connector_cubit.dart b/lib/xiao_connector/cubit/xiao_connector_cubit.dart index fa7f099..1742683 100644 --- a/lib/xiao_connector/cubit/xiao_connector_cubit.dart +++ b/lib/xiao_connector/cubit/xiao_connector_cubit.dart @@ -228,6 +228,12 @@ class XiaoConnectorCubit extends Cubit { emit(state.copyWith(status: XiaoConnectorStatus.connected)); } + Future disconnectDevice() async { + await device.disconnect(); + _services = []; + emit(state.copyWith(status: XiaoConnectorStatus.initial)); + } + void toggleRecording({required String captureType}) { isRecording = !isRecording; if (isRecording) { @@ -237,6 +243,7 @@ class XiaoConnectorCubit extends Cubit { CaptureBox( type: _captureType, uuid: _uuid, + startTime: DateTime.now(), ), ); } @@ -256,6 +263,18 @@ class XiaoConnectorCubit extends Cubit { return points; } - List getAllCaptureBoxes() => _captureBoxes.getAll(); + void deleteRecording(String uuid) { + _capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build().remove(); + _captureBoxes.query(CaptureBox_.uuid.equals(uuid)).build().remove(); + } + + List getAllCaptureBoxes() { + final query = (_captureBoxes.query() + ..order(CaptureBox_.startTime, flags: Order.descending)) + .build(); + final boxes = query.find(); + return boxes; + } + List getAllCapturePoints() => _capturePointsBox.getAll(); } diff --git a/lib/xiao_connector/models/capture_box.dart b/lib/xiao_connector/models/capture_box.dart index 596c73f..cfa2516 100644 --- a/lib/xiao_connector/models/capture_box.dart +++ b/lib/xiao_connector/models/capture_box.dart @@ -5,10 +5,14 @@ class CaptureBox { CaptureBox({ required this.type, required this.uuid, + required this.startTime, }); @Id() int id = 0; String type; String uuid; + + @Property(type: PropertyType.date) + DateTime startTime; } diff --git a/lib/xiao_connector/models/capture_point.dart b/lib/xiao_connector/models/capture_point.dart index 284fbb7..3091392 100644 --- a/lib/xiao_connector/models/capture_point.dart +++ b/lib/xiao_connector/models/capture_point.dart @@ -27,7 +27,6 @@ class CapturePoint { int accelerationY; int accelerationZ; - // @Property(type: PropertyType.date) int millisecondsSinceEpochReceived; int millisecondsSinceEpochSend; } diff --git a/lib/xiao_connector/view/connected_view.dart b/lib/xiao_connector/view/connected_view.dart index 598dc4a..3719d29 100644 --- a/lib/xiao_connector/view/connected_view.dart +++ b/lib/xiao_connector/view/connected_view.dart @@ -14,26 +14,36 @@ class ConnectedView extends StatelessWidget { title: const Text('Xiao Connector'), ), body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - ''' -You are connected to the Xiao Sense. -Now you can open the live feed.''', - textAlign: TextAlign.center, - ), - const SizedBox( - height: 8, - ), - ElevatedButton( - onPressed: () { - context.read().startCapturing(); - // context.read().setCapturingOn(); - }, - child: const Text('Open Live Feed'), - ), - ], + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + ''' + You are connected to the Xiao Sense. + Now you can open the live feed.''', + textAlign: TextAlign.center, + ), + const SizedBox( + height: 48, + ), + ElevatedButton( + onPressed: () { + context.read().startCapturing(); + }, + child: const Text('Open Live Feed'), + ), + const SizedBox( + height: 48, + ), + ElevatedButton( + onPressed: () { + context.read().disconnectDevice(); + }, + child: const Text('Disconnect'), + ), + ], + ), ), ), ); diff --git a/lib/xiao_connector/view/failure_view.dart b/lib/xiao_connector/view/failure_view.dart index 7205938..fd942e9 100644 --- a/lib/xiao_connector/view/failure_view.dart +++ b/lib/xiao_connector/view/failure_view.dart @@ -5,6 +5,6 @@ class FailureView extends StatelessWidget { @override Widget build(BuildContext context) { - return const Text('Failure'); + return const Text('Oops. Something bad happened! :('); } } diff --git a/xiao_controller_code/main.go b/xiao_controller_code/main.go index c0705ea..6d4b246 100644 --- a/xiao_controller_code/main.go +++ b/xiao_controller_code/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/binary" + "fmt" "machine" "time"