initial commit
This commit is contained in:
20
lib/app/app.dart
Normal file
20
lib/app/app.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:xiao_pet_tracker/app/view/app.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (BuildContext context) => XiaoConnectorCubit()..init(),
|
||||
),
|
||||
],
|
||||
child: const AppView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
106
lib/app/view/app.dart
Normal file
106
lib/app/view/app.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
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';
|
||||
import 'package:xiao_pet_tracker/app_router/app_router.dart';
|
||||
import 'package:xiao_pet_tracker/bootstrap.dart';
|
||||
import 'package:xiao_pet_tracker/l10n/l10n.dart';
|
||||
|
||||
class AppView extends StatefulWidget {
|
||||
const AppView({super.key});
|
||||
|
||||
@override
|
||||
State<AppView> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<AppView> {
|
||||
BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
|
||||
|
||||
late StreamSubscription<BluetoothAdapterState> _adapterStateStateSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_adapterStateStateSubscription =
|
||||
FlutterBluePlus.adapterState.listen((state) {
|
||||
_adapterState = state;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_adapterStateStateSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Widget screen = _adapterState == BluetoothAdapterState.on
|
||||
// ? const HomePage()
|
||||
// : BluetoothOffScreen(
|
||||
// adapterState: _adapterState,
|
||||
// );
|
||||
|
||||
// only allow portrait orientations
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
|
||||
return MaterialApp.router(
|
||||
title: 'Xiao Pet Tracker',
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
routerConfig: getIt<AppRouter>().config(),
|
||||
theme: ThemeData(),
|
||||
darkTheme: ThemeData.dark(),
|
||||
builder: (context, child) => FTheme(
|
||||
data: MediaQuery.of(context).platformBrightness == Brightness.light
|
||||
? FThemes.zinc.light
|
||||
: FThemes.zinc.dark,
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This observer listens for Bluetooth Off and dismisses the DeviceScreen
|
||||
class BluetoothAdapterStateObserver extends NavigatorObserver {
|
||||
StreamSubscription<BluetoothAdapterState>? _adapterStateSubscription;
|
||||
|
||||
@override
|
||||
void didPush(Route route, Route? previousRoute) {
|
||||
super.didPush(route, previousRoute);
|
||||
if (route.settings.name == '/DeviceScreen') {
|
||||
// Start listening to Bluetooth state changes when a new route is pushed
|
||||
_adapterStateSubscription ??= FlutterBluePlus.adapterState.listen(
|
||||
(state) {
|
||||
if (state != BluetoothAdapterState.on) {
|
||||
// Pop the current route if Bluetooth is off
|
||||
navigator?.pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPop(Route route, Route? previousRoute) {
|
||||
super.didPop(route, previousRoute);
|
||||
// Cancel the subscription when the route is popped
|
||||
_adapterStateSubscription?.cancel();
|
||||
_adapterStateSubscription = null;
|
||||
}
|
||||
}
|
||||
89
lib/app_router/app_router.dart
Normal file
89
lib/app_router/app_router.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:forui/forui.dart';
|
||||
import 'package:xiao_pet_tracker/app_router/app_router.gr.dart';
|
||||
|
||||
@AutoRouterConfig()
|
||||
class AppRouter extends RootStackRouter {
|
||||
@override
|
||||
RouteType get defaultRouteType => const RouteType.material();
|
||||
|
||||
@override
|
||||
List<AutoRoute> get routes => [
|
||||
AutoRoute(
|
||||
page: MainRoute.page,
|
||||
path: '/',
|
||||
children: [
|
||||
AutoRoute(
|
||||
page: XiaoConnectorRoute.page,
|
||||
),
|
||||
AutoRoute(
|
||||
page: RecordingsRoute.page,
|
||||
children: [],
|
||||
),
|
||||
],
|
||||
),
|
||||
AutoRoute(
|
||||
page: RecordingsDetailsRoute.page,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@RoutePage()
|
||||
class MainPage extends StatelessWidget {
|
||||
const MainPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AutoTabsScaffold(
|
||||
// transitionBuilder: (context, child, animation) => AnimatedBuilder(
|
||||
// animation: animation,
|
||||
// child: child,
|
||||
// // builder: (BuildContext context, Widget? child) {
|
||||
// // const begin = Offset(0.0, 0.1);
|
||||
// // const end = Offset.zero;
|
||||
// // final tween = Tween(begin: begin, end: end);
|
||||
// // final offsetAnimation = animation.drive(tween);
|
||||
// // return SlideTransition(
|
||||
// // position: offsetAnimation,
|
||||
// // child: Transform.scale(
|
||||
// // alignment: Alignment.bottomCenter,
|
||||
// // scale: animation.value,
|
||||
// // child: ClipPath(
|
||||
// // clipper: CircularRevealClipper(
|
||||
// // fraction: animation.value,
|
||||
// // centerAlignment: Alignment.bottomCenter,
|
||||
// // // centerOffset: centerOffset,
|
||||
// // // minRadius: minRadius,
|
||||
// // // maxRadius: maxRadius,
|
||||
// // ),
|
||||
// // child: child,
|
||||
// // ),
|
||||
// // ),
|
||||
// // );
|
||||
// // },
|
||||
// ),
|
||||
animationDuration: const Duration(milliseconds: 650),
|
||||
routes: const [
|
||||
XiaoConnectorRoute(),
|
||||
RecordingsRoute(),
|
||||
],
|
||||
bottomNavigationBuilder: (context, tabsRouter) {
|
||||
return FBottomNavigationBar(
|
||||
index: tabsRouter.activeIndex,
|
||||
onChange: tabsRouter.setActiveIndex,
|
||||
children: [
|
||||
FBottomNavigationBarItem(
|
||||
label: const Text('Xiao Connector'),
|
||||
icon: FIcon(FAssets.icons.microchip),
|
||||
),
|
||||
FBottomNavigationBarItem(
|
||||
label: const Text('Recordings'),
|
||||
icon: FIcon(FAssets.icons.disc3),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
149
lib/app_router/app_router.gr.dart
Normal file
149
lib/app_router/app_router.gr.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
// **************************************************************************
|
||||
// AutoRouterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// coverage:ignore-file
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:auto_route/auto_route.dart' as _i6;
|
||||
import 'package:flutter/material.dart' as _i7;
|
||||
import 'package:xiao_pet_tracker/app_router/app_router.dart' as _i1;
|
||||
import 'package:xiao_pet_tracker/recordings/view/recordings_details.dart'
|
||||
as _i2;
|
||||
import 'package:xiao_pet_tracker/recordings/view/recordings_page.dart' as _i3;
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/xiao_connector_page.dart'
|
||||
as _i4;
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/xiao_data_page.dart'
|
||||
as _i5;
|
||||
|
||||
/// generated route for
|
||||
/// [_i1.MainPage]
|
||||
class MainRoute extends _i6.PageRouteInfo<void> {
|
||||
const MainRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(
|
||||
MainRoute.name,
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'MainRoute';
|
||||
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i1.MainPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i2.RecordingsDetailsPage]
|
||||
class RecordingsDetailsRoute
|
||||
extends _i6.PageRouteInfo<RecordingsDetailsRouteArgs> {
|
||||
RecordingsDetailsRoute({
|
||||
required String uuid,
|
||||
required String type,
|
||||
_i7.Key? key,
|
||||
List<_i6.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
RecordingsDetailsRoute.name,
|
||||
args: RecordingsDetailsRouteArgs(
|
||||
uuid: uuid,
|
||||
type: type,
|
||||
key: key,
|
||||
),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'RecordingsDetailsRoute';
|
||||
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<RecordingsDetailsRouteArgs>();
|
||||
return _i2.RecordingsDetailsPage(
|
||||
uuid: args.uuid,
|
||||
type: args.type,
|
||||
key: args.key,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class RecordingsDetailsRouteArgs {
|
||||
const RecordingsDetailsRouteArgs({
|
||||
required this.uuid,
|
||||
required this.type,
|
||||
this.key,
|
||||
});
|
||||
|
||||
final String uuid;
|
||||
|
||||
final String type;
|
||||
|
||||
final _i7.Key? key;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RecordingsDetailsRouteArgs{uuid: $uuid, type: $type, key: $key}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i3.RecordingsPage]
|
||||
class RecordingsRoute extends _i6.PageRouteInfo<void> {
|
||||
const RecordingsRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(
|
||||
RecordingsRoute.name,
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'RecordingsRoute';
|
||||
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i3.RecordingsPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i4.XiaoConnectorPage]
|
||||
class XiaoConnectorRoute extends _i6.PageRouteInfo<void> {
|
||||
const XiaoConnectorRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(
|
||||
XiaoConnectorRoute.name,
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'XiaoConnectorRoute';
|
||||
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i4.XiaoConnectorPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i5.XiaoDataPage]
|
||||
class XiaoDataRoute extends _i6.PageRouteInfo<void> {
|
||||
const XiaoDataRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(
|
||||
XiaoDataRoute.name,
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'XiaoDataRoute';
|
||||
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i5.XiaoDataPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
44
lib/bootstrap.dart
Normal file
44
lib/bootstrap.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:xiao_pet_tracker/app_router/app_router.dart';
|
||||
import 'package:xiao_pet_tracker/objectbox.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
class AppBlocObserver extends BlocObserver {
|
||||
const AppBlocObserver();
|
||||
|
||||
@override
|
||||
void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {
|
||||
super.onChange(bloc, change);
|
||||
log('onChange(${bloc.runtimeType}, $change)');
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {
|
||||
log('onError(${bloc.runtimeType}, $error, $stackTrace)');
|
||||
super.onError(bloc, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
|
||||
FlutterError.onError = (details) {
|
||||
log(details.exceptionAsString(), stackTrace: details.stack);
|
||||
};
|
||||
|
||||
Bloc.observer = const AppBlocObserver();
|
||||
|
||||
// Add cross-flavor configuration here
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final AppRouter appRouter = getIt.registerSingleton(AppRouter());
|
||||
|
||||
final ObjectBox objectBox = getIt.registerSingleton(await ObjectBox.create());
|
||||
|
||||
runApp(await builder());
|
||||
}
|
||||
7
lib/l10n/arb/app_en.arb
Normal file
7
lib/l10n/arb/app_en.arb
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"counterAppBarTitle": "Counter",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
}
|
||||
}
|
||||
7
lib/l10n/arb/app_es.arb
Normal file
7
lib/l10n/arb/app_es.arb
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"@@locale": "es",
|
||||
"counterAppBarTitle": "Contador",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Texto mostrado en la AppBar de la página del contador"
|
||||
}
|
||||
}
|
||||
8
lib/l10n/l10n.dart
Normal file
8
lib/l10n/l10n.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
export 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
extension AppLocalizationsX on BuildContext {
|
||||
AppLocalizations get l10n => AppLocalizations.of(this);
|
||||
}
|
||||
8
lib/main_development.dart
Normal file
8
lib/main_development.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
|
||||
import 'package:xiao_pet_tracker/app/app.dart';
|
||||
import 'package:xiao_pet_tracker/bootstrap.dart';
|
||||
|
||||
void main() {
|
||||
FlutterBluePlus.setLogLevel(LogLevel.verbose);
|
||||
bootstrap(() => const App());
|
||||
}
|
||||
6
lib/main_production.dart
Normal file
6
lib/main_production.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:xiao_pet_tracker/app/app.dart';
|
||||
import 'package:xiao_pet_tracker/bootstrap.dart';
|
||||
|
||||
void main() {
|
||||
bootstrap(() => const App());
|
||||
}
|
||||
6
lib/main_staging.dart
Normal file
6
lib/main_staging.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:xiao_pet_tracker/app/app.dart';
|
||||
import 'package:xiao_pet_tracker/bootstrap.dart';
|
||||
|
||||
void main() {
|
||||
bootstrap(() => const App());
|
||||
}
|
||||
129
lib/objectbox-model.json
Normal file
129
lib/objectbox-model.json
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:8883777039160106085",
|
||||
"lastPropertyId": "28:5600095333294587898",
|
||||
"name": "CapturePoint",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:7031141970833098443",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:9089711773119462722",
|
||||
"name": "type",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "12:8018734828546727200",
|
||||
"name": "uuid",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "21:5571567596004635004",
|
||||
"name": "rotationX",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "22:128950109858162458",
|
||||
"name": "rotationY",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "23:572745053585623035",
|
||||
"name": "rotationZ",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "24:174662964807733060",
|
||||
"name": "accelerationX",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "25:2607558191101966289",
|
||||
"name": "accelerationY",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "26:8078744267935466967",
|
||||
"name": "accelerationZ",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "27:2235290443011784871",
|
||||
"name": "millisecondsSinceEpochReceived",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "28:5600095333294587898",
|
||||
"name": "millisecondsSinceEpochSend",
|
||||
"type": 6
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "2:1642885530071590702",
|
||||
"lastPropertyId": "4:5477306218303066701",
|
||||
"name": "CaptureBox",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:6772033063452157797",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:6118609604439630810",
|
||||
"name": "type",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "3:4134744801341910087",
|
||||
"name": "uuid",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "4:5477306218303066701",
|
||||
"name": "startTime",
|
||||
"type": 10
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "2:1642885530071590702",
|
||||
"lastIndexId": "0:0",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [
|
||||
5928562934131159642,
|
||||
5591187076567204697,
|
||||
595530508372810754,
|
||||
376663189826498742,
|
||||
2249450669900993772,
|
||||
3338488084769147616,
|
||||
6118432389289072923,
|
||||
652072338599943564,
|
||||
4974899424217069697,
|
||||
6174239378189558256,
|
||||
890878501153569237,
|
||||
1635504337236403097,
|
||||
3739937748546759727,
|
||||
1440167199258268261,
|
||||
90858017595682556,
|
||||
8864904344725714676,
|
||||
6079624305294726236
|
||||
],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
||||
18
lib/objectbox.dart
Normal file
18
lib/objectbox.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'objectbox.g.dart';
|
||||
|
||||
class ObjectBox {
|
||||
late final Store store;
|
||||
|
||||
/// Initialization
|
||||
ObjectBox._create(this.store);
|
||||
|
||||
/// Create an instance of ObjectBox to use throughout the app.
|
||||
static Future<ObjectBox> create() async {
|
||||
final docsDir = await getApplicationDocumentsDirectory();
|
||||
final store =
|
||||
await openStore(directory: p.join(docsDir.path, 'obx-pet-tracker-2'));
|
||||
return ObjectBox._create(store);
|
||||
}
|
||||
}
|
||||
352
lib/objectbox.g.dart
Normal file
352
lib/objectbox.g.dart
Normal file
@@ -0,0 +1,352 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// This code was generated by ObjectBox. To update it run the generator again
|
||||
// with `dart run build_runner build`.
|
||||
// See also https://docs.objectbox.io/getting-started#generate-objectbox-code
|
||||
|
||||
// ignore_for_file: camel_case_types, depend_on_referenced_packages
|
||||
// coverage:ignore-file
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||
import 'package:objectbox/internal.dart'
|
||||
as obx_int; // generated code can access "internal" functionality
|
||||
import 'package:objectbox/objectbox.dart' as obx;
|
||||
import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
|
||||
|
||||
import 'xiao_connector/models/capture_box.dart';
|
||||
import 'xiao_connector/models/capture_point.dart';
|
||||
|
||||
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
|
||||
|
||||
final _entities = <obx_int.ModelEntity>[
|
||||
obx_int.ModelEntity(
|
||||
id: const obx_int.IdUid(1, 8883777039160106085),
|
||||
name: 'CapturePoint',
|
||||
lastPropertyId: const obx_int.IdUid(28, 5600095333294587898),
|
||||
flags: 0,
|
||||
properties: <obx_int.ModelProperty>[
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(1, 7031141970833098443),
|
||||
name: 'id',
|
||||
type: 6,
|
||||
flags: 1),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(2, 9089711773119462722),
|
||||
name: 'type',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(12, 8018734828546727200),
|
||||
name: 'uuid',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(21, 5571567596004635004),
|
||||
name: 'rotationX',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(22, 128950109858162458),
|
||||
name: 'rotationY',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(23, 572745053585623035),
|
||||
name: 'rotationZ',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(24, 174662964807733060),
|
||||
name: 'accelerationX',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(25, 2607558191101966289),
|
||||
name: 'accelerationY',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(26, 8078744267935466967),
|
||||
name: 'accelerationZ',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(27, 2235290443011784871),
|
||||
name: 'millisecondsSinceEpochReceived',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(28, 5600095333294587898),
|
||||
name: 'millisecondsSinceEpochSend',
|
||||
type: 6,
|
||||
flags: 0)
|
||||
],
|
||||
relations: <obx_int.ModelRelation>[],
|
||||
backlinks: <obx_int.ModelBacklink>[]),
|
||||
obx_int.ModelEntity(
|
||||
id: const obx_int.IdUid(2, 1642885530071590702),
|
||||
name: 'CaptureBox',
|
||||
lastPropertyId: const obx_int.IdUid(4, 5477306218303066701),
|
||||
flags: 0,
|
||||
properties: <obx_int.ModelProperty>[
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(1, 6772033063452157797),
|
||||
name: 'id',
|
||||
type: 6,
|
||||
flags: 1),
|
||||
obx_int.ModelProperty(
|
||||
id: const obx_int.IdUid(2, 6118609604439630810),
|
||||
name: 'type',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
obx_int.ModelProperty(
|
||||
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: <obx_int.ModelRelation>[],
|
||||
backlinks: <obx_int.ModelBacklink>[])
|
||||
];
|
||||
|
||||
/// Shortcut for [obx.Store.new] that passes [getObjectBoxModel] and for Flutter
|
||||
/// apps by default a [directory] using `defaultStoreDirectory()` from the
|
||||
/// ObjectBox Flutter library.
|
||||
///
|
||||
/// Note: for desktop apps it is recommended to specify a unique [directory].
|
||||
///
|
||||
/// See [obx.Store.new] for an explanation of all parameters.
|
||||
///
|
||||
/// For Flutter apps, also calls `loadObjectBoxLibraryAndroidCompat()` from
|
||||
/// the ObjectBox Flutter library to fix loading the native ObjectBox library
|
||||
/// on Android 6 and older.
|
||||
Future<obx.Store> openStore(
|
||||
{String? directory,
|
||||
int? maxDBSizeInKB,
|
||||
int? maxDataSizeInKB,
|
||||
int? fileMode,
|
||||
int? maxReaders,
|
||||
bool queriesCaseSensitiveDefault = true,
|
||||
String? macosApplicationGroup}) async {
|
||||
await loadObjectBoxLibraryAndroidCompat();
|
||||
return obx.Store(getObjectBoxModel(),
|
||||
directory: directory ?? (await defaultStoreDirectory()).path,
|
||||
maxDBSizeInKB: maxDBSizeInKB,
|
||||
maxDataSizeInKB: maxDataSizeInKB,
|
||||
fileMode: fileMode,
|
||||
maxReaders: maxReaders,
|
||||
queriesCaseSensitiveDefault: queriesCaseSensitiveDefault,
|
||||
macosApplicationGroup: macosApplicationGroup);
|
||||
}
|
||||
|
||||
/// Returns the ObjectBox model definition for this project for use with
|
||||
/// [obx.Store.new].
|
||||
obx_int.ModelDefinition getObjectBoxModel() {
|
||||
final model = obx_int.ModelInfo(
|
||||
entities: _entities,
|
||||
lastEntityId: const obx_int.IdUid(2, 1642885530071590702),
|
||||
lastIndexId: const obx_int.IdUid(0, 0),
|
||||
lastRelationId: const obx_int.IdUid(0, 0),
|
||||
lastSequenceId: const obx_int.IdUid(0, 0),
|
||||
retiredEntityUids: const [],
|
||||
retiredIndexUids: const [],
|
||||
retiredPropertyUids: const [
|
||||
5928562934131159642,
|
||||
5591187076567204697,
|
||||
595530508372810754,
|
||||
376663189826498742,
|
||||
2249450669900993772,
|
||||
3338488084769147616,
|
||||
6118432389289072923,
|
||||
652072338599943564,
|
||||
4974899424217069697,
|
||||
6174239378189558256,
|
||||
890878501153569237,
|
||||
1635504337236403097,
|
||||
3739937748546759727,
|
||||
1440167199258268261,
|
||||
90858017595682556,
|
||||
8864904344725714676,
|
||||
6079624305294726236
|
||||
],
|
||||
retiredRelationUids: const [],
|
||||
modelVersion: 5,
|
||||
modelVersionParserMinimum: 5,
|
||||
version: 1);
|
||||
|
||||
final bindings = <Type, obx_int.EntityDefinition>{
|
||||
CapturePoint: obx_int.EntityDefinition<CapturePoint>(
|
||||
model: _entities[0],
|
||||
toOneRelations: (CapturePoint object) => [],
|
||||
toManyRelations: (CapturePoint object) => {},
|
||||
getId: (CapturePoint object) => object.id,
|
||||
setId: (CapturePoint object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (CapturePoint object, fb.Builder fbb) {
|
||||
final typeOffset = fbb.writeString(object.type);
|
||||
final uuidOffset = fbb.writeString(object.uuid);
|
||||
fbb.startTable(29);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, typeOffset);
|
||||
fbb.addOffset(11, uuidOffset);
|
||||
fbb.addInt64(20, object.rotationX);
|
||||
fbb.addInt64(21, object.rotationY);
|
||||
fbb.addInt64(22, object.rotationZ);
|
||||
fbb.addInt64(23, object.accelerationX);
|
||||
fbb.addInt64(24, object.accelerationY);
|
||||
fbb.addInt64(25, object.accelerationZ);
|
||||
fbb.addInt64(26, object.millisecondsSinceEpochReceived);
|
||||
fbb.addInt64(27, object.millisecondsSinceEpochSend);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (obx.Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
final typeParam = const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, '');
|
||||
final uuidParam = const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 26, '');
|
||||
final rotationXParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0);
|
||||
final rotationYParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 46, 0);
|
||||
final rotationZParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 48, 0);
|
||||
final accelerationXParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 50, 0);
|
||||
final accelerationYParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 52, 0);
|
||||
final accelerationZParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 54, 0);
|
||||
final millisecondsSinceEpochReceivedParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 56, 0);
|
||||
final millisecondsSinceEpochSendParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 58, 0);
|
||||
final object = CapturePoint(
|
||||
type: typeParam,
|
||||
uuid: uuidParam,
|
||||
rotationX: rotationXParam,
|
||||
rotationY: rotationYParam,
|
||||
rotationZ: rotationZParam,
|
||||
accelerationX: accelerationXParam,
|
||||
accelerationY: accelerationYParam,
|
||||
accelerationZ: accelerationZParam,
|
||||
millisecondsSinceEpochReceived:
|
||||
millisecondsSinceEpochReceivedParam,
|
||||
millisecondsSinceEpochSend: millisecondsSinceEpochSendParam)
|
||||
..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
|
||||
|
||||
return object;
|
||||
}),
|
||||
CaptureBox: obx_int.EntityDefinition<CaptureBox>(
|
||||
model: _entities[1],
|
||||
toOneRelations: (CaptureBox object) => [],
|
||||
toManyRelations: (CaptureBox object) => {},
|
||||
getId: (CaptureBox object) => object.id,
|
||||
setId: (CaptureBox object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (CaptureBox object, fb.Builder fbb) {
|
||||
final typeOffset = fbb.writeString(object.type);
|
||||
final uuidOffset = fbb.writeString(object.uuid);
|
||||
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;
|
||||
},
|
||||
objectFromFB: (obx.Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
final typeParam = const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, '');
|
||||
final uuidParam = const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 8, '');
|
||||
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;
|
||||
})
|
||||
};
|
||||
|
||||
return obx_int.ModelDefinition(model, bindings);
|
||||
}
|
||||
|
||||
/// [CapturePoint] entity fields to define ObjectBox queries.
|
||||
class CapturePoint_ {
|
||||
/// See [CapturePoint.id].
|
||||
static final id =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[0]);
|
||||
|
||||
/// See [CapturePoint.type].
|
||||
static final type =
|
||||
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[1]);
|
||||
|
||||
/// See [CapturePoint.uuid].
|
||||
static final uuid =
|
||||
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[2]);
|
||||
|
||||
/// See [CapturePoint.rotationX].
|
||||
static final rotationX =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[3]);
|
||||
|
||||
/// See [CapturePoint.rotationY].
|
||||
static final rotationY =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[4]);
|
||||
|
||||
/// See [CapturePoint.rotationZ].
|
||||
static final rotationZ =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[5]);
|
||||
|
||||
/// See [CapturePoint.accelerationX].
|
||||
static final accelerationX =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[6]);
|
||||
|
||||
/// See [CapturePoint.accelerationY].
|
||||
static final accelerationY =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[7]);
|
||||
|
||||
/// See [CapturePoint.accelerationZ].
|
||||
static final accelerationZ =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[8]);
|
||||
|
||||
/// See [CapturePoint.millisecondsSinceEpochReceived].
|
||||
static final millisecondsSinceEpochReceived =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[9]);
|
||||
|
||||
/// See [CapturePoint.millisecondsSinceEpochSend].
|
||||
static final millisecondsSinceEpochSend =
|
||||
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[10]);
|
||||
}
|
||||
|
||||
/// [CaptureBox] entity fields to define ObjectBox queries.
|
||||
class CaptureBox_ {
|
||||
/// See [CaptureBox.id].
|
||||
static final id =
|
||||
obx.QueryIntegerProperty<CaptureBox>(_entities[1].properties[0]);
|
||||
|
||||
/// See [CaptureBox.type].
|
||||
static final type =
|
||||
obx.QueryStringProperty<CaptureBox>(_entities[1].properties[1]);
|
||||
|
||||
/// See [CaptureBox.uuid].
|
||||
static final uuid =
|
||||
obx.QueryStringProperty<CaptureBox>(_entities[1].properties[2]);
|
||||
|
||||
/// See [CaptureBox.startTime].
|
||||
static final startTime =
|
||||
obx.QueryDateProperty<CaptureBox>(_entities[1].properties[3]);
|
||||
}
|
||||
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';
|
||||
2
lib/xiao_connector/constants/constants.dart
Normal file
2
lib/xiao_connector/constants/constants.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
const uuidLSM6DS3TRService = '4c534d36-4453-3354-5253-657276696365';
|
||||
const uuidAccelerationData = '61636365-6c65-7261-7469-6f6e44617461';
|
||||
280
lib/xiao_connector/cubit/xiao_connector_cubit.dart
Normal file
280
lib/xiao_connector/cubit/xiao_connector_cubit.dart
Normal file
@@ -0,0 +1,280 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:xiao_pet_tracker/bootstrap.dart';
|
||||
import 'package:xiao_pet_tracker/objectbox.dart';
|
||||
import 'package:xiao_pet_tracker/objectbox.g.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/utils/utils.dart';
|
||||
|
||||
part 'xiao_connector_state.dart';
|
||||
|
||||
class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
|
||||
XiaoConnectorCubit() : super(const XiaoConnectorState());
|
||||
|
||||
late BluetoothDevice device;
|
||||
List<BluetoothService> _services = [];
|
||||
late BluetoothCharacteristic _senseService;
|
||||
|
||||
late StreamSubscription<List<int>> dataCapturingSubscription;
|
||||
|
||||
final ObjectBox _objectBox = getIt<ObjectBox>();
|
||||
late final Box<CapturePoint> _capturePointsBox;
|
||||
late final Box<CaptureBox> _captureBoxes;
|
||||
|
||||
bool gotRotation = false;
|
||||
bool gotAcceleration = false;
|
||||
bool gotTimeStamp = false;
|
||||
|
||||
int _rotX = 0;
|
||||
int _rotY = 0;
|
||||
int _rotZ = 0;
|
||||
int _accX = 0;
|
||||
int _accY = 0;
|
||||
int _accZ = 0;
|
||||
int _millisecondsSinceEpochSend = 0;
|
||||
String _captureType = '';
|
||||
String _uuid = '';
|
||||
|
||||
bool isRecording = false;
|
||||
|
||||
Future<void> init() async {
|
||||
_capturePointsBox = _objectBox.store.box<CapturePoint>();
|
||||
_captureBoxes = _objectBox.store.box<CaptureBox>();
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.loading));
|
||||
|
||||
try {
|
||||
// scan for devices for 15 seconds
|
||||
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.failure));
|
||||
throw Exception('Start Scan Error: $e');
|
||||
}
|
||||
|
||||
FlutterBluePlus.scanResults.listen(
|
||||
(results) async {
|
||||
for (final r in results) {
|
||||
if (kDebugMode) {
|
||||
print(r);
|
||||
}
|
||||
|
||||
// set the xiao sense name
|
||||
if (r.device.advName == 'Go Bluetooth 2') {
|
||||
await FlutterBluePlus.stopScan();
|
||||
device = r.device;
|
||||
|
||||
await device.connect();
|
||||
|
||||
try {
|
||||
_services = await device.discoverServices();
|
||||
} catch (e) {
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.failure));
|
||||
throw Exception('Discover Services Error: $e');
|
||||
}
|
||||
|
||||
device.connectionState
|
||||
.listen((BluetoothConnectionState blueState) async {
|
||||
if (blueState == BluetoothConnectionState.disconnected) {
|
||||
debugPrint('DISCONNECTED!');
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.initial));
|
||||
}
|
||||
});
|
||||
|
||||
// device.cancelWhenDisconnected(deviceConnectionStream);
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.connected));
|
||||
}
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
debugPrint('DONE');
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.initial));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setCapturingOn() async {
|
||||
final senseService = _services
|
||||
.where(
|
||||
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
|
||||
)
|
||||
.first
|
||||
.characteristics
|
||||
.where(
|
||||
(c) =>
|
||||
c.characteristicUuid ==
|
||||
Guid('63617074-7572-696E-6753-657276696365'),
|
||||
)
|
||||
.first;
|
||||
|
||||
await senseService.write([1]);
|
||||
}
|
||||
|
||||
Uint8List int64BigEndianBytes(int value) =>
|
||||
Uint8List(8)..buffer.asByteData().setInt64(0, value);
|
||||
|
||||
Future<void> setTime(int millisSinceEpoch) async {
|
||||
final senseService = _services
|
||||
.where(
|
||||
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
|
||||
)
|
||||
.first
|
||||
.characteristics
|
||||
.where(
|
||||
(c) =>
|
||||
c.characteristicUuid ==
|
||||
Guid('756E6978-5469-6D65-5374-616D70527374'),
|
||||
)
|
||||
.first;
|
||||
|
||||
final List<int> bytes = int64BigEndianBytes(millisSinceEpoch);
|
||||
await senseService.write(bytes);
|
||||
}
|
||||
|
||||
Future<void> startCapturing() async {
|
||||
_senseService = _services
|
||||
.where(
|
||||
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
|
||||
)
|
||||
.first
|
||||
.characteristics
|
||||
.where(
|
||||
(c) =>
|
||||
c.characteristicUuid ==
|
||||
Guid('61636365-6c65-7261-7469-6f6e44617461'),
|
||||
)
|
||||
.first;
|
||||
|
||||
dataCapturingSubscription = _senseService.onValueReceived.listen((value) {
|
||||
// rotation
|
||||
if (value.last == 1) {
|
||||
_rotX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
|
||||
_rotY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
|
||||
_rotZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
|
||||
gotRotation = true;
|
||||
}
|
||||
|
||||
// acceleration
|
||||
if (value.last == 2) {
|
||||
_accX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
|
||||
_accY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
|
||||
_accZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
|
||||
gotAcceleration = true;
|
||||
}
|
||||
|
||||
if (value.last == 3) {
|
||||
final timeStamp = fromBytesToInt64(
|
||||
value[0],
|
||||
value[1],
|
||||
value[2],
|
||||
value[3],
|
||||
value[4],
|
||||
value[5],
|
||||
value[6],
|
||||
value[7],
|
||||
);
|
||||
_millisecondsSinceEpochSend = timeStamp;
|
||||
gotTimeStamp = true;
|
||||
}
|
||||
|
||||
if (gotAcceleration && gotRotation && gotTimeStamp) {
|
||||
final capturePoint = CapturePoint(
|
||||
type: _captureType,
|
||||
uuid: _uuid,
|
||||
rotationX: _rotX,
|
||||
rotationY: _rotY,
|
||||
rotationZ: _rotZ,
|
||||
accelerationX: _accX,
|
||||
accelerationY: _accY,
|
||||
accelerationZ: _accZ,
|
||||
millisecondsSinceEpochReceived:
|
||||
DateTime.now().toUtc().millisecondsSinceEpoch,
|
||||
millisecondsSinceEpochSend: _millisecondsSinceEpochSend,
|
||||
);
|
||||
emit(state.copyWith(lastCapturedPoint: capturePoint));
|
||||
|
||||
if (isRecording) {
|
||||
_writeToObjectBox(capturePoint: capturePoint);
|
||||
}
|
||||
|
||||
gotAcceleration = false;
|
||||
gotRotation = false;
|
||||
gotTimeStamp = false;
|
||||
}
|
||||
});
|
||||
await setCapturingOn();
|
||||
|
||||
await setTime(DateTime.now().millisecondsSinceEpoch);
|
||||
|
||||
device.cancelWhenDisconnected(dataCapturingSubscription);
|
||||
|
||||
await _senseService.setNotifyValue(true);
|
||||
|
||||
emit(state.copyWith(status: XiaoConnectorStatus.capturing));
|
||||
}
|
||||
|
||||
Future<void> stopCapturing() async {
|
||||
await dataCapturingSubscription.cancel();
|
||||
|
||||
await _senseService.setNotifyValue(false);
|
||||
|
||||
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}) {
|
||||
isRecording = !isRecording;
|
||||
if (isRecording) {
|
||||
_captureType = captureType;
|
||||
_uuid = const Uuid().v4();
|
||||
_captureBoxes.put(
|
||||
CaptureBox(
|
||||
type: _captureType,
|
||||
uuid: _uuid,
|
||||
startTime: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _writeToObjectBox({
|
||||
required CapturePoint capturePoint,
|
||||
}) {
|
||||
_capturePointsBox.put(capturePoint);
|
||||
}
|
||||
|
||||
List<CapturePoint> getCapturePointsOfUuid(String uuid) {
|
||||
final query =
|
||||
_capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build();
|
||||
|
||||
final points = query.find();
|
||||
return points;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
40
lib/xiao_connector/cubit/xiao_connector_state.dart
Normal file
40
lib/xiao_connector/cubit/xiao_connector_state.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
part of 'xiao_connector_cubit.dart';
|
||||
|
||||
enum XiaoConnectorStatus {
|
||||
initial,
|
||||
loading,
|
||||
connected,
|
||||
capturing,
|
||||
failure,
|
||||
}
|
||||
|
||||
extension XiaoConnectorStatusX on XiaoConnectorStatus {
|
||||
bool get isInitial => this == XiaoConnectorStatus.initial;
|
||||
bool get isLoading => this == XiaoConnectorStatus.loading;
|
||||
bool get isConnected => this == XiaoConnectorStatus.connected;
|
||||
bool get isCapturing => this == XiaoConnectorStatus.capturing;
|
||||
bool get isFailure => this == XiaoConnectorStatus.failure;
|
||||
}
|
||||
|
||||
final class XiaoConnectorState extends Equatable {
|
||||
const XiaoConnectorState({
|
||||
this.status = XiaoConnectorStatus.initial,
|
||||
this.lastCapturedPoint,
|
||||
});
|
||||
|
||||
final XiaoConnectorStatus status;
|
||||
final CapturePoint? lastCapturedPoint;
|
||||
|
||||
XiaoConnectorState copyWith({
|
||||
XiaoConnectorStatus? status,
|
||||
CapturePoint? lastCapturedPoint,
|
||||
}) {
|
||||
return XiaoConnectorState(
|
||||
status: status ?? this.status,
|
||||
lastCapturedPoint: lastCapturedPoint ?? this.lastCapturedPoint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, lastCapturedPoint];
|
||||
}
|
||||
18
lib/xiao_connector/models/capture_box.dart
Normal file
18
lib/xiao_connector/models/capture_box.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
@Entity()
|
||||
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;
|
||||
}
|
||||
32
lib/xiao_connector/models/capture_point.dart
Normal file
32
lib/xiao_connector/models/capture_point.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
@Entity()
|
||||
class CapturePoint {
|
||||
CapturePoint({
|
||||
required this.type,
|
||||
required this.uuid,
|
||||
required this.rotationX,
|
||||
required this.rotationY,
|
||||
required this.rotationZ,
|
||||
required this.accelerationX,
|
||||
required this.accelerationY,
|
||||
required this.accelerationZ,
|
||||
required this.millisecondsSinceEpochReceived,
|
||||
required this.millisecondsSinceEpochSend,
|
||||
});
|
||||
@Id()
|
||||
int id = 0;
|
||||
|
||||
String type;
|
||||
String uuid;
|
||||
|
||||
int rotationX;
|
||||
int rotationY;
|
||||
int rotationZ;
|
||||
int accelerationX;
|
||||
int accelerationY;
|
||||
int accelerationZ;
|
||||
|
||||
int millisecondsSinceEpochReceived;
|
||||
int millisecondsSinceEpochSend;
|
||||
}
|
||||
32
lib/xiao_connector/utils/utils.dart
Normal file
32
lib/xiao_connector/utils/utils.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
int fromBytesToInt32(int b3, int b2, int b1, int b0) {
|
||||
final int8List = Int8List(4)
|
||||
..[3] = b3
|
||||
..[2] = b2
|
||||
..[1] = b1
|
||||
..[0] = b0;
|
||||
return int8List.buffer.asByteData().getInt32(0);
|
||||
}
|
||||
|
||||
int fromBytesToInt64(
|
||||
int b7,
|
||||
int b6,
|
||||
int b5,
|
||||
int b4,
|
||||
int b3,
|
||||
int b2,
|
||||
int b1,
|
||||
int b0,
|
||||
) {
|
||||
final int8List = Int8List(8)
|
||||
..[7] = b7
|
||||
..[6] = b6
|
||||
..[5] = b5
|
||||
..[4] = b4
|
||||
..[3] = b3
|
||||
..[2] = b2
|
||||
..[1] = b1
|
||||
..[0] = b0;
|
||||
return int8List.buffer.asByteData().getInt64(0);
|
||||
}
|
||||
193
lib/xiao_connector/view/capture_view.dart
Normal file
193
lib/xiao_connector/view/capture_view.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
|
||||
|
||||
class CaptureView extends StatefulWidget {
|
||||
const CaptureView({super.key});
|
||||
|
||||
@override
|
||||
State<CaptureView> createState() => _CaptureViewState();
|
||||
}
|
||||
|
||||
class _CaptureViewState extends State<CaptureView> {
|
||||
late TextEditingController _controller;
|
||||
bool _isTextFieldFocused = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final count = context.select((XiaoConnectorCubit cubit) => cubit.state);
|
||||
final lastCapturePoint = context
|
||||
.select((XiaoConnectorCubit cubit) => cubit.state.lastCapturedPoint);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Live Feed'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<XiaoConnectorCubit>().stopCapturing();
|
||||
},
|
||||
child: const Text('Close Live Feed'),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
const Text(
|
||||
'Last Captured Point',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Divider(
|
||||
indent: 80,
|
||||
endIndent: 80,
|
||||
),
|
||||
const Text(
|
||||
'Received Time',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'${DateTime.fromMillisecondsSinceEpoch(
|
||||
lastCapturePoint?.millisecondsSinceEpochReceived ?? 0,
|
||||
)}',
|
||||
),
|
||||
const Text(
|
||||
'Send Time',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Text('(time the controller send the data)'),
|
||||
Text(
|
||||
'${DateTime.fromMillisecondsSinceEpoch(
|
||||
lastCapturePoint?.millisecondsSinceEpochSend ?? 0,
|
||||
)}',
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// context.read<XiaoConnectorCubit>().startCapturing(
|
||||
// captureType: 'test',
|
||||
// );
|
||||
final timeToSend = DateTime.now().millisecondsSinceEpoch;
|
||||
context.read<XiaoConnectorCubit>().setTime(timeToSend);
|
||||
},
|
||||
icon: const Icon(Icons.watch_later_outlined),
|
||||
label: const Text('Sync Clocks'),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
const Divider(
|
||||
indent: 80,
|
||||
endIndent: 80,
|
||||
),
|
||||
Text('Acceleration X: ${lastCapturePoint?.accelerationX}'),
|
||||
Text('Acceleration Y: ${lastCapturePoint?.accelerationY}'),
|
||||
Text('Acceleration Z: ${lastCapturePoint?.accelerationZ}'),
|
||||
const Divider(
|
||||
indent: 80,
|
||||
endIndent: 80,
|
||||
),
|
||||
Text('Rotation X: ${lastCapturePoint?.rotationX}'),
|
||||
Text('Rotation Y: ${lastCapturePoint?.rotationY}'),
|
||||
Text('Rotation Z: ${lastCapturePoint?.rotationZ}'),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32, right: 32, top: 16),
|
||||
child: FocusScope(
|
||||
child: Focus(
|
||||
onFocusChange: (focus) {
|
||||
_isTextFieldFocused = focus;
|
||||
setState(() {});
|
||||
},
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Capture Name'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: _isTextFieldFocused
|
||||
? null
|
||||
: FloatingActionButton.large(
|
||||
child: context.read<XiaoConnectorCubit>().isRecording
|
||||
? const Stack(
|
||||
children: [
|
||||
Align(
|
||||
child: Icon(Icons.pause),
|
||||
),
|
||||
Align(
|
||||
child: SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const Icon(Icons.play_arrow),
|
||||
onPressed: () {
|
||||
if (_controller.value.text == '') {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
'Please enter Capture Name',
|
||||
),
|
||||
content: const Text(
|
||||
'You need to enter a capture name before you can start capturing data.',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Ok',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
context.read<XiaoConnectorCubit>().toggleRecording(
|
||||
captureType: _controller.value.text,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
51
lib/xiao_connector/view/connected_view.dart
Normal file
51
lib/xiao_connector/view/connected_view.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
|
||||
|
||||
class ConnectedView extends StatelessWidget {
|
||||
const ConnectedView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Xiao Connector'),
|
||||
),
|
||||
body: Center(
|
||||
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<XiaoConnectorCubit>().startCapturing();
|
||||
},
|
||||
child: const Text('Open Live Feed'),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<XiaoConnectorCubit>().disconnectDevice();
|
||||
},
|
||||
child: const Text('Disconnect'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
10
lib/xiao_connector/view/failure_view.dart
Normal file
10
lib/xiao_connector/view/failure_view.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FailureView extends StatelessWidget {
|
||||
const FailureView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Text('Oops. Something bad happened! :(');
|
||||
}
|
||||
}
|
||||
38
lib/xiao_connector/view/initial_view.dart
Normal file
38
lib/xiao_connector/view/initial_view.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
|
||||
|
||||
class InitialView extends StatelessWidget {
|
||||
const InitialView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Xiao Connector'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'''
|
||||
You are currently not connected to the Xiao Sense.
|
||||
Click on `Connect` to try to connect to the device.''',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<XiaoConnectorCubit>().connect();
|
||||
},
|
||||
child: const Text('Connect'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
12
lib/xiao_connector/view/loading_view.dart
Normal file
12
lib/xiao_connector/view/loading_view.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoadingView extends StatelessWidget {
|
||||
const LoadingView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
}
|
||||
1
lib/xiao_connector/view/view.dart
Normal file
1
lib/xiao_connector/view/view.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'xiao_connector_page.dart';
|
||||
40
lib/xiao_connector/view/xiao_connector_page.dart
Normal file
40
lib/xiao_connector/view/xiao_connector_page.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/capture_view.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/connected_view.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/failure_view.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/initial_view.dart';
|
||||
import 'package:xiao_pet_tracker/xiao_connector/view/loading_view.dart';
|
||||
|
||||
@RoutePage()
|
||||
class XiaoConnectorPage extends StatelessWidget {
|
||||
const XiaoConnectorPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const XiaoConnectorView();
|
||||
}
|
||||
}
|
||||
|
||||
class XiaoConnectorView extends StatelessWidget {
|
||||
const XiaoConnectorView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocBuilder<XiaoConnectorCubit, XiaoConnectorState>(
|
||||
builder: (context, state) {
|
||||
return switch (state.status) {
|
||||
XiaoConnectorStatus.initial => const InitialView(),
|
||||
XiaoConnectorStatus.loading => const LoadingView(),
|
||||
XiaoConnectorStatus.connected => const ConnectedView(),
|
||||
XiaoConnectorStatus.capturing => const CaptureView(),
|
||||
XiaoConnectorStatus.failure => const FailureView(),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
22
lib/xiao_connector/view/xiao_data_page.dart
Normal file
22
lib/xiao_connector/view/xiao_data_page.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class XiaoDataPage extends StatelessWidget {
|
||||
const XiaoDataPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
|
||||
class XiaoDataView extends StatelessWidget {
|
||||
const XiaoDataView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
1
lib/xiao_connector/xiao_connector.dart
Normal file
1
lib/xiao_connector/xiao_connector.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'view/view.dart';
|
||||
Reference in New Issue
Block a user