add some ui improvements
Some checks failed
xiao_pet_tracker / semantic-pull-request (push) Failing after 2s
xiao_pet_tracker / build (push) Failing after 2s
xiao_pet_tracker / spell-check (push) Failing after 1s

This commit is contained in:
baldeau 2024-11-14 10:36:22 +01:00
parent 9690161207
commit 003e982ff8
11 changed files with 180 additions and 69 deletions

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart'; import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:forui/forui.dart'; import 'package:forui/forui.dart';
@ -46,6 +47,12 @@ class _AppState extends State<AppView> {
// adapterState: _adapterState, // adapterState: _adapterState,
// ); // );
// only allow portrait orientations
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MaterialApp.router( return MaterialApp.router(
title: 'Xiao Pet Tracker', title: 'Xiao Pet Tracker',
localizationsDelegates: const [ localizationsDelegates: const [

View File

@ -69,7 +69,7 @@
}, },
{ {
"id": "2:1642885530071590702", "id": "2:1642885530071590702",
"lastPropertyId": "3:4134744801341910087", "lastPropertyId": "4:5477306218303066701",
"name": "CaptureBox", "name": "CaptureBox",
"properties": [ "properties": [
{ {
@ -87,6 +87,11 @@
"id": "3:4134744801341910087", "id": "3:4134744801341910087",
"name": "uuid", "name": "uuid",
"type": 9 "type": 9
},
{
"id": "4:5477306218303066701",
"name": "startTime",
"type": 10
} }
], ],
"relations": [] "relations": []

View File

@ -87,7 +87,7 @@ final _entities = <obx_int.ModelEntity>[
obx_int.ModelEntity( obx_int.ModelEntity(
id: const obx_int.IdUid(2, 1642885530071590702), id: const obx_int.IdUid(2, 1642885530071590702),
name: 'CaptureBox', name: 'CaptureBox',
lastPropertyId: const obx_int.IdUid(3, 4134744801341910087), lastPropertyId: const obx_int.IdUid(4, 5477306218303066701),
flags: 0, flags: 0,
properties: <obx_int.ModelProperty>[ properties: <obx_int.ModelProperty>[
obx_int.ModelProperty( obx_int.ModelProperty(
@ -104,6 +104,11 @@ final _entities = <obx_int.ModelEntity>[
id: const obx_int.IdUid(3, 4134744801341910087), id: const obx_int.IdUid(3, 4134744801341910087),
name: 'uuid', name: 'uuid',
type: 9, type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(4, 5477306218303066701),
name: 'startTime',
type: 10,
flags: 0) flags: 0)
], ],
relations: <obx_int.ModelRelation>[], relations: <obx_int.ModelRelation>[],
@ -252,10 +257,11 @@ obx_int.ModelDefinition getObjectBoxModel() {
objectToFB: (CaptureBox object, fb.Builder fbb) { objectToFB: (CaptureBox object, fb.Builder fbb) {
final typeOffset = fbb.writeString(object.type); final typeOffset = fbb.writeString(object.type);
final uuidOffset = fbb.writeString(object.uuid); final uuidOffset = fbb.writeString(object.uuid);
fbb.startTable(4); fbb.startTable(5);
fbb.addInt64(0, object.id); fbb.addInt64(0, object.id);
fbb.addOffset(1, typeOffset); fbb.addOffset(1, typeOffset);
fbb.addOffset(2, uuidOffset); fbb.addOffset(2, uuidOffset);
fbb.addInt64(3, object.startTime.millisecondsSinceEpoch);
fbb.finish(fbb.endTable()); fbb.finish(fbb.endTable());
return object.id; return object.id;
}, },
@ -266,7 +272,10 @@ obx_int.ModelDefinition getObjectBoxModel() {
.vTableGet(buffer, rootOffset, 6, ''); .vTableGet(buffer, rootOffset, 6, '');
final uuidParam = const fb.StringReader(asciiOptimization: true) final uuidParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 8, ''); .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); ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
return object; return object;
@ -336,4 +345,8 @@ class CaptureBox_ {
/// See [CaptureBox.uuid]. /// See [CaptureBox.uuid].
static final uuid = static final uuid =
obx.QueryStringProperty<CaptureBox>(_entities[1].properties[2]); obx.QueryStringProperty<CaptureBox>(_entities[1].properties[2]);
/// See [CaptureBox.startTime].
static final startTime =
obx.QueryDateProperty<CaptureBox>(_entities[1].properties[3]);
} }

View File

@ -5,10 +5,9 @@ import 'package:csv/csv.dart';
import 'package:ditredi/ditredi.dart'; import 'package:ditredi/ditredi.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:ditredi/ditredi.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.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: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/cubit/xiao_connector_cubit.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart'; import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
@ -31,8 +30,11 @@ class RecordingsDetailsPage extends StatefulWidget {
class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> { class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
final _controllerRotation = DiTreDiController(); final _controllerRotation = DiTreDiController();
final _controllerAcceleration = DiTreDiController(); final _controllerAcceleration = DiTreDiController();
final f = DateFormat('hh:mm:ss.SSS');
List<CapturePoint> _capturePoints = []; List<CapturePoint> _capturePoints = [];
bool isStoringCSV = false;
@override @override
void initState() { void initState() {
_capturePoints = _capturePoints =
@ -54,10 +56,10 @@ class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
height: 16, height: 16,
), ),
Text( Text(
'Capture Start Time: ${DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend)}', 'Start Time: ${f.format(DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend))}',
), ),
Text( Text(
'Capture End Time: ${DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend)}', 'End Time: ${f.format(DateTime.fromMillisecondsSinceEpoch(_capturePoints.last.millisecondsSinceEpochSend))}',
), ),
const SizedBox( const SizedBox(
height: 8, height: 8,
@ -66,50 +68,68 @@ class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
ElevatedButton( if (isStoringCSV)
onPressed: () async { const CircularProgressIndicator()
// await Permission.storage.request(); else
ElevatedButton(
onPressed: () async {
setState(() {
isStoringCSV = true;
});
// await Permission.storage.request();
final Directory? downloadsDir = (Platform.isIOS) final Directory? downloadsDir = (Platform.isIOS)
? await getApplicationDocumentsDirectory() ? await getApplicationDocumentsDirectory()
: await getDownloadsDirectory(); : await getDownloadsDirectory();
File f = File( File f = File(downloadsDir!.path +
downloadsDir!.path + "/${widget.type}_${widget.uuid}.csv"); "/${widget.type}_${widget.uuid}.csv");
List<List<dynamic>> rows = []; List<List<dynamic>> rows = [];
List<dynamic> 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<dynamic> row = []; List<dynamic> row = [];
row.add(_capturePoints[i].millisecondsSinceEpochSend); row.add('sendTimeStamp');
row.add(_capturePoints[i].millisecondsSinceEpochReceived); row.add('receivedTimeStamp');
row.add(_capturePoints[i].accelerationX); row.add('accelerationX');
row.add(_capturePoints[i].accelerationY); row.add('accelerationY');
row.add(_capturePoints[i].accelerationZ); row.add('accelerationZ');
row.add(_capturePoints[i].rotationX); row.add('rotationX');
row.add(_capturePoints[i].rotationY); row.add('rotationY');
row.add(_capturePoints[i].rotationZ); row.add('rotationZ');
rows.add(row); rows.add(row);
}
String csv = const ListToCsvConverter().convert(rows); for (var i = 0; i < _capturePoints.length; i++) {
List<dynamic> 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); String csv = const ListToCsvConverter().convert(rows);
},
child: const Text('Export to CSV'), 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( const SizedBox(
height: 16, height: 16,
), ),

View File

@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:forui/forui.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/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/cubit/xiao_connector_cubit.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.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() @RoutePage()
class RecordingsPage extends StatelessWidget { class RecordingsPage extends StatelessWidget {
@ -26,6 +26,7 @@ class RecordingsView extends StatefulWidget {
class _SettingsViewState extends State<RecordingsView> { class _SettingsViewState extends State<RecordingsView> {
List<CaptureBox> _captureBoxes = []; List<CaptureBox> _captureBoxes = [];
final f = DateFormat('hh:mm - dd.MM.yyyy ');
@override @override
void initState() { void initState() {
@ -47,7 +48,7 @@ class _SettingsViewState extends State<RecordingsView> {
return ListTile( return ListTile(
leading: FIcon(FAssets.icons.pawPrint), leading: FIcon(FAssets.icons.pawPrint),
title: Text('Collection Type: ${_captureBoxes[i].type}'), title: Text('Collection Type: ${_captureBoxes[i].type}'),
subtitle: Text('Uuid: ${_captureBoxes[i].uuid}'), subtitle: Text(f.format(_captureBoxes[i].startTime)),
onTap: () { onTap: () {
AutoRouter.of(context).push( AutoRouter.of(context).push(
RecordingsDetailsRoute( RecordingsDetailsRoute(
@ -56,6 +57,38 @@ class _SettingsViewState extends State<RecordingsView> {
), ),
); );
}, },
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<XiaoConnectorCubit>()
.deleteRecording(_captureBoxes[i].uuid);
_pullRefresh();
Navigator.of(context).pop();
},
child: const Text('Delete'),
),
],
);
},
);
},
); );
}, },
), ),

View File

@ -228,6 +228,12 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
emit(state.copyWith(status: XiaoConnectorStatus.connected)); emit(state.copyWith(status: XiaoConnectorStatus.connected));
} }
Future<void> disconnectDevice() async {
await device.disconnect();
_services = [];
emit(state.copyWith(status: XiaoConnectorStatus.initial));
}
void toggleRecording({required String captureType}) { void toggleRecording({required String captureType}) {
isRecording = !isRecording; isRecording = !isRecording;
if (isRecording) { if (isRecording) {
@ -237,6 +243,7 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
CaptureBox( CaptureBox(
type: _captureType, type: _captureType,
uuid: _uuid, uuid: _uuid,
startTime: DateTime.now(),
), ),
); );
} }
@ -256,6 +263,18 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
return points; return points;
} }
List<CaptureBox> getAllCaptureBoxes() => _captureBoxes.getAll(); void deleteRecording(String uuid) {
_capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build().remove();
_captureBoxes.query(CaptureBox_.uuid.equals(uuid)).build().remove();
}
List<CaptureBox> getAllCaptureBoxes() {
final query = (_captureBoxes.query()
..order(CaptureBox_.startTime, flags: Order.descending))
.build();
final boxes = query.find();
return boxes;
}
List<CapturePoint> getAllCapturePoints() => _capturePointsBox.getAll(); List<CapturePoint> getAllCapturePoints() => _capturePointsBox.getAll();
} }

View File

@ -5,10 +5,14 @@ class CaptureBox {
CaptureBox({ CaptureBox({
required this.type, required this.type,
required this.uuid, required this.uuid,
required this.startTime,
}); });
@Id() @Id()
int id = 0; int id = 0;
String type; String type;
String uuid; String uuid;
@Property(type: PropertyType.date)
DateTime startTime;
} }

View File

@ -27,7 +27,6 @@ class CapturePoint {
int accelerationY; int accelerationY;
int accelerationZ; int accelerationZ;
// @Property(type: PropertyType.date)
int millisecondsSinceEpochReceived; int millisecondsSinceEpochReceived;
int millisecondsSinceEpochSend; int millisecondsSinceEpochSend;
} }

View File

@ -14,26 +14,36 @@ class ConnectedView extends StatelessWidget {
title: const Text('Xiao Connector'), title: const Text('Xiao Connector'),
), ),
body: Center( body: Center(
child: Column( child: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.center,
const Text( children: [
''' const Text(
You are connected to the Xiao Sense. '''
Now you can open the live feed.''', You are connected to the Xiao Sense.
textAlign: TextAlign.center, Now you can open the live feed.''',
), textAlign: TextAlign.center,
const SizedBox( ),
height: 8, const SizedBox(
), height: 48,
ElevatedButton( ),
onPressed: () { ElevatedButton(
context.read<XiaoConnectorCubit>().startCapturing(); onPressed: () {
// context.read<XiaoConnectorCubit>().setCapturingOn(); context.read<XiaoConnectorCubit>().startCapturing();
}, },
child: const Text('Open Live Feed'), child: const Text('Open Live Feed'),
), ),
], const SizedBox(
height: 48,
),
ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().disconnectDevice();
},
child: const Text('Disconnect'),
),
],
),
), ),
), ),
); );

View File

@ -5,6 +5,6 @@ class FailureView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Text('Failure'); return const Text('Oops. Something bad happened! :(');
} }
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"machine" "machine"
"time" "time"