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,16 +68,22 @@ class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
if (isStoringCSV)
const CircularProgressIndicator()
else
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
setState(() {
isStoringCSV = true;
});
// await Permission.storage.request(); // 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 = [];
@ -107,6 +115,18 @@ class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
String csv = const ListToCsvConverter().convert(rows); String csv = const ListToCsvConverter().convert(rows);
f.writeAsString(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'), child: const Text('Export to CSV'),
), ),

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,28 +14,38 @@ class ConnectedView extends StatelessWidget {
title: const Text('Xiao Connector'), title: const Text('Xiao Connector'),
), ),
body: Center( body: Center(
child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text( const Text(
''' '''
You are connected to the Xiao Sense. You are connected to the Xiao Sense.
Now you can open the live feed.''', Now you can open the live feed.''',
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox( const SizedBox(
height: 8, height: 48,
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.read<XiaoConnectorCubit>().startCapturing(); context.read<XiaoConnectorCubit>().startCapturing();
// context.read<XiaoConnectorCubit>().setCapturingOn();
}, },
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"