initial commit
This commit is contained in:
1
lib/recordings/recordings.dart
Normal file
1
lib/recordings/recordings.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'view/view.dart';
|
||||
228
lib/recordings/view/recordings_details.dart
Normal file
228
lib/recordings/view/recordings_details.dart
Normal 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'),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
103
lib/recordings/view/recordings_page.dart
Normal file
103
lib/recordings/view/recordings_page.dart
Normal 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(() {});
|
||||
}
|
||||
}
|
||||
1
lib/recordings/view/view.dart
Normal file
1
lib/recordings/view/view.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'recordings_page.dart';
|
||||
Reference in New Issue
Block a user