initial commit
Some checks failed
xiao_pet_tracker / semantic-pull-request (push) Has been cancelled
xiao_pet_tracker / build (push) Has been cancelled
xiao_pet_tracker / spell-check (push) Has been cancelled

This commit is contained in:
2025-02-23 20:50:34 +01:00
commit 2fe59fee0d
360 changed files with 14384 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:csv/csv.dart';
import 'package:ditredi/ditredi.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.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';
@RoutePage()
class RecordingsDetailsPage extends StatefulWidget {
const RecordingsDetailsPage({
required this.uuid,
required this.type,
super.key,
});
final String uuid;
final String type;
@override
State<RecordingsDetailsPage> createState() => _RecordingsDetailsPageState();
}
class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
final _controllerRotation = DiTreDiController();
final _controllerAcceleration = DiTreDiController();
final f = DateFormat('hh:mm:ss.SSS');
List<CapturePoint> _capturePoints = [];
bool isStoringCSV = false;
@override
void initState() {
_capturePoints =
context.read<XiaoConnectorCubit>().getCapturePointsOfUuid(widget.uuid);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details: ${widget.type}'),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(
height: 16,
),
Text(
'Start Time: ${f.format(DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend))}',
),
Text(
'End Time: ${f.format(DateTime.fromMillisecondsSinceEpoch(_capturePoints.last.millisecondsSinceEpochSend))}',
),
const SizedBox(
height: 8,
),
Text('Amount of captured points: ${_capturePoints.length}'),
const SizedBox(
height: 16,
),
if (isStoringCSV)
const CircularProgressIndicator()
else
ElevatedButton(
onPressed: () async {
setState(() {
isStoringCSV = true;
});
// await Permission.storage.request();
final downloadsDir = (Platform.isIOS)
? await getApplicationDocumentsDirectory()
: await getDownloadsDirectory();
final f = File(
'${downloadsDir!.path}/${widget.type}_${widget.uuid}.csv',
);
final rows = <List<dynamic>>[];
rows.add([
'sendTimeStamp',
'receivedTimeStamp',
'accelerationX',
'accelerationY',
'accelerationZ',
'rotationX',
'rotationY',
'rotationZ',
]);
for (var i = 0; i < _capturePoints.length; i++) {
rows.add([
_capturePoints[i].millisecondsSinceEpochSend,
_capturePoints[i].millisecondsSinceEpochReceived,
_capturePoints[i].accelerationX,
_capturePoints[i].accelerationY,
_capturePoints[i].accelerationZ,
_capturePoints[i].rotationX,
_capturePoints[i].rotationY,
_capturePoints[i].rotationZ,
]);
}
final csv = const ListToCsvConverter().convert(rows);
await 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,
),
const Text('Raw Rotation Data'),
Padding(
padding: const EdgeInsets.all(8),
child: Container(
height: 400,
// width: 400,
// color: Color.fromARGB(255, 0, 0, 0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 0, 0, 0),
border:
Border.all(color: const Color.fromARGB(255, 0, 0, 0))),
child: DiTreDiDraggable(
controller: _controllerRotation,
child: DiTreDi(
figures: [
Cube3D(
0,
Vector3(0, 0, 0),
color: const Color.fromARGB(0, 128, 0, 0),
),
..._capturePoints.map(
(p) => Point3D(
Vector3(
p.rotationX.toDouble(),
p.rotationY.toDouble(),
p.rotationZ.toDouble(),
),
width: 2,
color: const Color.fromARGB(255, 255, 255, 255),
),
)
],
controller: _controllerRotation,
),
),
),
),
const Text('Raw Acceleration Data'),
Padding(
padding: const EdgeInsets.all(8),
child: Container(
height: 400,
// width: 400,
// color: Color.fromARGB(255, 0, 0, 0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 0, 0, 0),
border:
Border.all(color: const Color.fromARGB(255, 0, 0, 0))),
child: DiTreDiDraggable(
controller: _controllerAcceleration,
child: DiTreDi(
figures: [
Cube3D(
0,
Vector3(0, 0, 0),
color: const Color.fromARGB(0, 128, 0, 0),
),
..._capturePoints.map(
(p) => Point3D(
Vector3(
p.accelerationX.toDouble(),
p.accelerationY.toDouble(),
p.accelerationZ.toDouble(),
),
width: 2,
color: const Color.fromARGB(255, 255, 255, 255),
),
)
],
controller: _controllerAcceleration,
),
),
),
),
const SizedBox(
height: 100,
),
// ListView.builder(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
// itemCount: _capturePoints.length,
// itemBuilder: (context, i) {
// return ListTile(
// title: Text('data'),
// );
// },
// ),
],
),
),
);
}
}

View File

@@ -0,0 +1,103 @@
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';
@RoutePage()
class RecordingsPage extends StatelessWidget {
const RecordingsPage({super.key});
@override
Widget build(BuildContext context) {
return const RecordingsView();
}
}
class RecordingsView extends StatefulWidget {
const RecordingsView({super.key});
@override
State<RecordingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<RecordingsView> {
List<CaptureBox> _captureBoxes = [];
final f = DateFormat('hh:mm - dd.MM.yyyy ');
@override
void initState() {
_captureBoxes = context.read<XiaoConnectorCubit>().getAllCaptureBoxes();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Recordings'),
),
body: RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: _captureBoxes.length,
itemBuilder: (context, i) {
return ListTile(
leading: FIcon(FAssets.icons.pawPrint),
title: Text('Collection Type: ${_captureBoxes[i].type}'),
subtitle: Text(f.format(_captureBoxes[i].startTime)),
onTap: () {
AutoRouter.of(context).push(
RecordingsDetailsRoute(
type: _captureBoxes[i].type,
uuid: _captureBoxes[i].uuid,
),
);
},
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'),
),
],
);
},
);
},
);
},
),
),
);
}
Future<void> _pullRefresh() async {
_captureBoxes = context.read<XiaoConnectorCubit>().getAllCaptureBoxes();
setState(() {});
}
}

View File

@@ -0,0 +1 @@
export 'recordings_page.dart';