diff --git a/.nvim.lua b/.nvim.lua
new file mode 100644
index 0000000..7c5744d
--- /dev/null
+++ b/.nvim.lua
@@ -0,0 +1,19 @@
+require("flutter-tools").setup_project({
+ {
+ name = "Development",
+ flavor = "development",
+ target = "lib/main_development.dart",
+ },
+ {
+ name = "Development Simulator",
+ flavor = "development",
+ target = "lib/main_development.dart",
+ device = "658DAB68-40FB-4DEA-8C0B-C92451326570",
+ },
+ {
+ name = "Production",
+ flavor = "production",
+ target = "lib/main_production.dart",
+ device = "658DAB68-40FB-4DEA-8C0B-C92451326570",
+ },
+})
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 7fd8db3..af6b17e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -51,7 +51,8 @@ android {
applicationId "com.example.verygoodcore.xiao_pet_tracker"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
- minSdkVersion flutter.minSdkVersion
+ // ObjectBox Sync requires at least SDK 21 (Android 5.0)
+ minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2e95815..07a763b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -32,5 +32,7 @@
+
+
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 37d8c86..bf87ae7 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -2,20 +2,27 @@ PODS:
- Flutter (1.0.0)
- flutter_blue_plus (0.0.1):
- Flutter
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_blue_plus (from `.symlinks/plugins/flutter_blue_plus/ios`)
+ - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_blue_plus:
:path: ".symlinks/plugins/flutter_blue_plus/ios"
+ path_provider_foundation:
+ :path: ".symlinks/plugins/path_provider_foundation/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_blue_plus: 4837da7d00cf5d441fdd6635b3a57f936778ea96
+ path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
diff --git a/lib/app/app.dart b/lib/app/app.dart
index f23ab3c..ae71c5c 100644
--- a/lib/app/app.dart
+++ b/lib/app/app.dart
@@ -1 +1,20 @@
-export 'view/app.dart';
+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(),
+ );
+ }
+}
diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart
index 1001d7c..f1473aa 100644
--- a/lib/app/view/app.dart
+++ b/lib/app/view/app.dart
@@ -2,19 +2,22 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
-import 'package:shadcn_ui/shadcn_ui.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/home/view/home_page.dart';
import 'package:xiao_pet_tracker/l10n/l10n.dart';
import 'package:xiao_pet_tracker/screens/bluetooth_off_screen.dart';
-class App extends StatefulWidget {
- const App({super.key});
+class AppView extends StatefulWidget {
+ const AppView({super.key});
@override
- State createState() => _AppState();
+ State createState() => _AppState();
}
-class _AppState extends State {
+class _AppState extends State {
BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
late StreamSubscription _adapterStateStateSubscription;
@@ -45,11 +48,25 @@ class _AppState extends State {
adapterState: _adapterState,
);
- return ShadApp.material(
- localizationsDelegates: AppLocalizations.localizationsDelegates,
+ return MaterialApp.router(
+ title: 'Xiao Pet Tracker',
+ localizationsDelegates: const [
+ AppLocalizations.delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ ],
+ // localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
- navigatorObservers: [BluetoothAdapterStateObserver()],
- home: screen,
+ routerConfig: getIt().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!,
+ ),
);
}
}
@@ -63,13 +80,14 @@ class BluetoothAdapterStateObserver extends NavigatorObserver {
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();
- }
- });
+ _adapterStateSubscription ??= FlutterBluePlus.adapterState.listen(
+ (state) {
+ if (state != BluetoothAdapterState.on) {
+ // Pop the current route if Bluetooth is off
+ navigator?.pop();
+ }
+ },
+ );
}
}
diff --git a/lib/app_router/app_router.dart b/lib/app_router/app_router.dart
new file mode 100644
index 0000000..55659fc
--- /dev/null
+++ b/lib/app_router/app_router.dart
@@ -0,0 +1,88 @@
+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 get routes => [
+ AutoRoute(
+ page: MainRoute.page,
+ path: '/',
+ children: [
+ AutoRoute(
+ page: HomeRoute.page,
+ ),
+ AutoRoute(
+ page: XiaoConnectorRoute.page,
+ ),
+ AutoRoute(
+ page: SettingsRoute.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(),
+ SettingsRoute(),
+ ],
+ bottomNavigationBuilder: (context, tabsRouter) {
+ return FBottomNavigationBar(
+ index: tabsRouter.activeIndex,
+ onChange: tabsRouter.setActiveIndex,
+ children: [
+ FBottomNavigationBarItem(
+ label: const Text('Home'),
+ icon: FIcon(FAssets.icons.house),
+ ),
+ FBottomNavigationBarItem(
+ label: const Text('Settings'),
+ icon: FIcon(FAssets.icons.settings),
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/app_router/app_router.gr.dart b/lib/app_router/app_router.gr.dart
new file mode 100644
index 0000000..1423936
--- /dev/null
+++ b/lib/app_router/app_router.gr.dart
@@ -0,0 +1,92 @@
+// 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 _i5;
+import 'package:xiao_pet_tracker/app_router/app_router.dart' as _i2;
+import 'package:xiao_pet_tracker/home/view/home_page.dart' as _i1;
+import 'package:xiao_pet_tracker/settings/view/settings_page.dart' as _i3;
+import 'package:xiao_pet_tracker/xiao_connector/view/xiao_connector_page.dart'
+ as _i4;
+
+/// generated route for
+/// [_i1.HomePage]
+class HomeRoute extends _i5.PageRouteInfo {
+ const HomeRoute({List<_i5.PageRouteInfo>? children})
+ : super(
+ HomeRoute.name,
+ initialChildren: children,
+ );
+
+ static const String name = 'HomeRoute';
+
+ static _i5.PageInfo page = _i5.PageInfo(
+ name,
+ builder: (data) {
+ return const _i1.HomePage();
+ },
+ );
+}
+
+/// generated route for
+/// [_i2.MainPage]
+class MainRoute extends _i5.PageRouteInfo {
+ const MainRoute({List<_i5.PageRouteInfo>? children})
+ : super(
+ MainRoute.name,
+ initialChildren: children,
+ );
+
+ static const String name = 'MainRoute';
+
+ static _i5.PageInfo page = _i5.PageInfo(
+ name,
+ builder: (data) {
+ return const _i2.MainPage();
+ },
+ );
+}
+
+/// generated route for
+/// [_i3.SettingsPage]
+class SettingsRoute extends _i5.PageRouteInfo {
+ const SettingsRoute({List<_i5.PageRouteInfo>? children})
+ : super(
+ SettingsRoute.name,
+ initialChildren: children,
+ );
+
+ static const String name = 'SettingsRoute';
+
+ static _i5.PageInfo page = _i5.PageInfo(
+ name,
+ builder: (data) {
+ return const _i3.SettingsPage();
+ },
+ );
+}
+
+/// generated route for
+/// [_i4.XiaoConnectorPage]
+class XiaoConnectorRoute extends _i5.PageRouteInfo {
+ const XiaoConnectorRoute({List<_i5.PageRouteInfo>? children})
+ : super(
+ XiaoConnectorRoute.name,
+ initialChildren: children,
+ );
+
+ static const String name = 'XiaoConnectorRoute';
+
+ static _i5.PageInfo page = _i5.PageInfo(
+ name,
+ builder: (data) {
+ return const _i4.XiaoConnectorPage();
+ },
+ );
+}
diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart
index c173883..5591fc2 100644
--- a/lib/bootstrap.dart
+++ b/lib/bootstrap.dart
@@ -3,6 +3,11 @@ 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();
@@ -31,5 +36,9 @@ Future bootstrap(FutureOr Function() builder) async {
WidgetsFlutterBinding.ensureInitialized();
+ final AppRouter appRouter = getIt.registerSingleton(AppRouter());
+
+ final ObjectBox objectBox = getIt.registerSingleton(await ObjectBox.create());
+
runApp(await builder());
}
diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart
index c63226f..2a95b95 100644
--- a/lib/home/view/home_page.dart
+++ b/lib/home/view/home_page.dart
@@ -1,10 +1,11 @@
import 'dart:async';
+import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
-import 'package:shadcn_ui/shadcn_ui.dart';
+@RoutePage()
class HomePage extends StatelessWidget {
const HomePage({super.key});
@@ -199,8 +200,8 @@ class _HomeScreenState extends State {
// },
// ),
if (_connected) Text('Connected!'),
- ShadButton(
- enabled: !_isScanning,
+ ElevatedButton(
+ // enabled: !_isScanning,
onPressed: () async {
// await Future.delayed(const Duration(seconds: 2));
// onScanPressed();
@@ -210,19 +211,19 @@ class _HomeScreenState extends State {
onConnectPressed();
}
},
- icon: _isScanning
- ? const SizedBox.square(
- dimension: 16,
- child: CircularProgressIndicator(),
- )
- : null,
+ // icon: _isScanning
+ // ? const SizedBox.square(
+ // dimension: 16,
+ // child: CircularProgressIndicator(),
+ // )
+ // : null,
child: _isScanning
? const Text('Please wait')
: _connected
? const Text('Disconnect')
: const Text('Connect'),
),
- ShadButton(
+ ElevatedButton(
onPressed: () async {
print('Toggle LED');
print(_services);
@@ -250,7 +251,7 @@ class _HomeScreenState extends State {
},
child: Text('Toggle LED'),
),
- ShadButton(
+ ElevatedButton(
onPressed: () async {
print('Toggle LED');
print(_services);
@@ -258,21 +259,32 @@ class _HomeScreenState extends State {
.where(
(s) =>
s.serviceUuid ==
- Guid('a0b40003-926d-4d61-98df-8c5c62ee53b3'),
+ Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
- .where((c) =>
- c.characteristicUuid ==
- Guid('a0b40004-926d-4d61-98df-8c5c62ee53b3'))
+ .where(
+ (c) =>
+ c.characteristicUuid ==
+ Guid('61636365-6c65-7261-7469-6f6e44617461'),
+ )
.first;
+ final senseSubscription =
+ ledService.onValueReceived.listen((value) {
+ print("UPDATE: ${String.fromCharCodes(value)}");
+ });
+
+ device!.cancelWhenDisconnected(senseSubscription);
+
+ await ledService.setNotifyValue(true);
+
// await ledService.write([1, 2, 3]);
// await Future.delayed(Duration(seconds: 2));
- List ledServiceValue = await ledService.read();
- print("READ Sense SERVICE: $ledServiceValue");
- print(
- "READ Sense SERVICE: ${String.fromCharCodes(ledServiceValue)}");
+ // List ledServiceValue = await ledService.read();
+ // print("READ Sense SERVICE: $ledServiceValue");
+ // print(
+ // "READ Sense SERVICE: ${String.fromCharCodes(ledServiceValue)}");
},
child: Text('Read Sense'),
)
diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json
new file mode 100644
index 0000000..f43b2c5
--- /dev/null
+++ b/lib/objectbox-model.json
@@ -0,0 +1,84 @@
+{
+ "_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": "2:23371849066855038",
+ "lastPropertyId": "10:4052938287274443905",
+ "name": "CapturePoint",
+ "properties": [
+ {
+ "id": "1:9209282349575641428",
+ "name": "id",
+ "type": 6,
+ "flags": 1
+ },
+ {
+ "id": "2:8441629944202144182",
+ "name": "rotationX",
+ "type": 9
+ },
+ {
+ "id": "3:3805193742978753321",
+ "name": "rotationY",
+ "type": 9
+ },
+ {
+ "id": "4:7632102454091387966",
+ "name": "rotationZ",
+ "type": 9
+ },
+ {
+ "id": "5:1175247580397683930",
+ "name": "accelerationX",
+ "type": 9
+ },
+ {
+ "id": "6:133552602314920473",
+ "name": "accelerationY",
+ "type": 9
+ },
+ {
+ "id": "7:7272664476171529007",
+ "name": "accelerationZ",
+ "type": 9
+ },
+ {
+ "id": "9:5697556233714735057",
+ "name": "type",
+ "type": 9
+ },
+ {
+ "id": "10:4052938287274443905",
+ "name": "millisecondsSinceEpoch",
+ "type": 6
+ }
+ ],
+ "relations": []
+ }
+ ],
+ "lastEntityId": "2:23371849066855038",
+ "lastIndexId": "0:0",
+ "lastRelationId": "0:0",
+ "lastSequenceId": "0:0",
+ "modelVersion": 5,
+ "modelVersionParserMinimum": 5,
+ "retiredEntityUids": [
+ 6206931177901930822
+ ],
+ "retiredIndexUids": [],
+ "retiredPropertyUids": [
+ 696937191447548336,
+ 7375635633551465262,
+ 7972299062087181706,
+ 5044874535120106939,
+ 6473009355337784814,
+ 8948425706967252950,
+ 5134040367670771080,
+ 4879888592430478383,
+ 2890906455219707986
+ ],
+ "retiredRelationUids": [],
+ "version": 1
+}
\ No newline at end of file
diff --git a/lib/objectbox.dart b/lib/objectbox.dart
new file mode 100644
index 0000000..219aae7
--- /dev/null
+++ b/lib/objectbox.dart
@@ -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 create() async {
+ final docsDir = await getApplicationDocumentsDirectory();
+ final store =
+ await openStore(directory: p.join(docsDir.path, 'obx-pet-tracker'));
+ return ObjectBox._create(store);
+ }
+}
diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart
new file mode 100644
index 0000000..b0e6031
--- /dev/null
+++ b/lib/objectbox.g.dart
@@ -0,0 +1,242 @@
+// 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_point.dart';
+
+export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
+
+final _entities = [
+ obx_int.ModelEntity(
+ id: const obx_int.IdUid(2, 23371849066855038),
+ name: 'CapturePoint',
+ lastPropertyId: const obx_int.IdUid(10, 4052938287274443905),
+ flags: 0,
+ properties: [
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(1, 9209282349575641428),
+ name: 'id',
+ type: 6,
+ flags: 1),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(2, 8441629944202144182),
+ name: 'rotationX',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(3, 3805193742978753321),
+ name: 'rotationY',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(4, 7632102454091387966),
+ name: 'rotationZ',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(5, 1175247580397683930),
+ name: 'accelerationX',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(6, 133552602314920473),
+ name: 'accelerationY',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(7, 7272664476171529007),
+ name: 'accelerationZ',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(9, 5697556233714735057),
+ name: 'type',
+ type: 9,
+ flags: 0),
+ obx_int.ModelProperty(
+ id: const obx_int.IdUid(10, 4052938287274443905),
+ name: 'millisecondsSinceEpoch',
+ type: 6,
+ flags: 0)
+ ],
+ relations: [],
+ backlinks: [])
+];
+
+/// 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 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, 23371849066855038),
+ lastIndexId: const obx_int.IdUid(0, 0),
+ lastRelationId: const obx_int.IdUid(0, 0),
+ lastSequenceId: const obx_int.IdUid(0, 0),
+ retiredEntityUids: const [6206931177901930822],
+ retiredIndexUids: const [],
+ retiredPropertyUids: const [
+ 696937191447548336,
+ 7375635633551465262,
+ 7972299062087181706,
+ 5044874535120106939,
+ 6473009355337784814,
+ 8948425706967252950,
+ 5134040367670771080,
+ 4879888592430478383,
+ 2890906455219707986
+ ],
+ retiredRelationUids: const [],
+ modelVersion: 5,
+ modelVersionParserMinimum: 5,
+ version: 1);
+
+ final bindings = {
+ CapturePoint: obx_int.EntityDefinition(
+ 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 rotationXOffset = fbb.writeString(object.rotationX);
+ final rotationYOffset = fbb.writeString(object.rotationY);
+ final rotationZOffset = fbb.writeString(object.rotationZ);
+ final accelerationXOffset = fbb.writeString(object.accelerationX);
+ final accelerationYOffset = fbb.writeString(object.accelerationY);
+ final accelerationZOffset = fbb.writeString(object.accelerationZ);
+ final typeOffset = fbb.writeString(object.type);
+ fbb.startTable(11);
+ fbb.addInt64(0, object.id);
+ fbb.addOffset(1, rotationXOffset);
+ fbb.addOffset(2, rotationYOffset);
+ fbb.addOffset(3, rotationZOffset);
+ fbb.addOffset(4, accelerationXOffset);
+ fbb.addOffset(5, accelerationYOffset);
+ fbb.addOffset(6, accelerationZOffset);
+ fbb.addOffset(8, typeOffset);
+ fbb.addInt64(9, object.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, 20, '');
+ final rotationXParam = const fb.StringReader(asciiOptimization: true)
+ .vTableGet(buffer, rootOffset, 6, '');
+ final rotationYParam = const fb.StringReader(asciiOptimization: true)
+ .vTableGet(buffer, rootOffset, 8, '');
+ final rotationZParam = const fb.StringReader(asciiOptimization: true)
+ .vTableGet(buffer, rootOffset, 10, '');
+ final accelerationXParam =
+ const fb.StringReader(asciiOptimization: true)
+ .vTableGet(buffer, rootOffset, 12, '');
+ final accelerationYParam =
+ const fb.StringReader(asciiOptimization: true)
+ .vTableGet(buffer, rootOffset, 14, '');
+ final accelerationZParam =
+ const fb.StringReader(asciiOptimization: true)
+ .vTableGet(buffer, rootOffset, 16, '');
+ final millisecondsSinceEpochParam =
+ const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 22);
+ final object = CapturePoint(
+ type: typeParam,
+ rotationX: rotationXParam,
+ rotationY: rotationYParam,
+ rotationZ: rotationZParam,
+ accelerationX: accelerationXParam,
+ accelerationY: accelerationYParam,
+ accelerationZ: accelerationZParam,
+ millisecondsSinceEpoch: millisecondsSinceEpochParam)
+ ..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(_entities[0].properties[0]);
+
+ /// See [CapturePoint.rotationX].
+ static final rotationX =
+ obx.QueryStringProperty(_entities[0].properties[1]);
+
+ /// See [CapturePoint.rotationY].
+ static final rotationY =
+ obx.QueryStringProperty(_entities[0].properties[2]);
+
+ /// See [CapturePoint.rotationZ].
+ static final rotationZ =
+ obx.QueryStringProperty(_entities[0].properties[3]);
+
+ /// See [CapturePoint.accelerationX].
+ static final accelerationX =
+ obx.QueryStringProperty(_entities[0].properties[4]);
+
+ /// See [CapturePoint.accelerationY].
+ static final accelerationY =
+ obx.QueryStringProperty(_entities[0].properties[5]);
+
+ /// See [CapturePoint.accelerationZ].
+ static final accelerationZ =
+ obx.QueryStringProperty(_entities[0].properties[6]);
+
+ /// See [CapturePoint.type].
+ static final type =
+ obx.QueryStringProperty(_entities[0].properties[7]);
+
+ /// See [CapturePoint.millisecondsSinceEpoch].
+ static final millisecondsSinceEpoch =
+ obx.QueryIntegerProperty(_entities[0].properties[8]);
+}
diff --git a/lib/settings/settings.dart b/lib/settings/settings.dart
new file mode 100644
index 0000000..00ffcf9
--- /dev/null
+++ b/lib/settings/settings.dart
@@ -0,0 +1 @@
+export 'view/view.dart';
diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart
new file mode 100644
index 0000000..af122cd
--- /dev/null
+++ b/lib/settings/view/settings_page.dart
@@ -0,0 +1,23 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+
+@RoutePage()
+class SettingsPage extends StatelessWidget {
+ const SettingsPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const SettingsView();
+ }
+}
+
+class SettingsView extends StatelessWidget {
+ const SettingsView({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Center(
+ child: Text('Settings'),
+ );
+ }
+}
diff --git a/lib/settings/view/view.dart b/lib/settings/view/view.dart
new file mode 100644
index 0000000..10ba3ac
--- /dev/null
+++ b/lib/settings/view/view.dart
@@ -0,0 +1 @@
+export 'settings_page.dart';
diff --git a/lib/xiao_connector/constants/constants.dart b/lib/xiao_connector/constants/constants.dart
new file mode 100644
index 0000000..565fa08
--- /dev/null
+++ b/lib/xiao_connector/constants/constants.dart
@@ -0,0 +1,6 @@
+const uuidLSM6DS3TRService = '4c534d36-4453-3354-5253-657276696365';
+const uuidAccelerationData = '61636365-6c65-7261-7469-6f6e44617461';
+
+
+ //tempSenseService = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x53, 0x65, 0x6E, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
+ //temperatureSense = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x6E, 0x73, 0x65}
diff --git a/lib/xiao_connector/cubit/xiao_connector_cubit.dart b/lib/xiao_connector/cubit/xiao_connector_cubit.dart
new file mode 100644
index 0000000..2128bed
--- /dev/null
+++ b/lib/xiao_connector/cubit/xiao_connector_cubit.dart
@@ -0,0 +1,194 @@
+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:objectbox/objectbox.dart';
+import 'package:xiao_pet_tracker/bootstrap.dart';
+import 'package:xiao_pet_tracker/objectbox.dart';
+import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
+
+part 'xiao_connector_state.dart';
+
+class XiaoConnectorCubit extends Cubit {
+ XiaoConnectorCubit() : super(const XiaoConnectorState());
+
+ late BluetoothDevice device;
+ List _systemDevices = [];
+ List _services = [];
+
+ late StreamSubscription> dataCapturingSubscription;
+
+ final ObjectBox _objectBox = getIt();
+ late final Box _capturePointsBox;
+
+ bool gotRotationX = false;
+ bool gotRotationY = false;
+ bool gotRotationZ = false;
+ bool gotAcceleration = false;
+
+ late String _rotX;
+ late String _rotY;
+ late String _rotZ;
+ late String _accel;
+
+ Future init() async {
+ _capturePointsBox = _objectBox.store.box();
+ }
+
+ Future connect() async {
+ emit(state.copyWith(status: XiaoConnectorStatus.loading));
+ try {
+ _systemDevices = await FlutterBluePlus.systemDevices([]);
+ } catch (e) {
+ emit(state.copyWith(status: XiaoConnectorStatus.failure));
+ throw Exception('System Devices Error: $e');
+ }
+
+ 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') {
+ 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 startCapturing({required String captureType}) async {
+ final 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) {
+ debugPrint('UPDATE: $value');
+ debugPrint('UPDATE: ${String.fromCharCodes(value)}');
+
+ final valueAsString = String.fromCharCodes(value);
+
+ if (valueAsString.startsWith('rX=')) {
+ gotRotationX = true;
+ _rotX = valueAsString.replaceAll('rX=', '');
+ }
+
+ if (valueAsString.startsWith('rY=')) {
+ gotRotationY = true;
+ _rotY = valueAsString.replaceAll('rY=', '');
+ }
+
+ if (valueAsString.startsWith('rZ=')) {
+ gotRotationZ = true;
+ _rotZ = valueAsString.replaceAll('rZ=', '');
+ }
+
+ if (valueAsString.contains(',')) {
+ gotAcceleration = true;
+ _accel = valueAsString;
+ }
+
+ if (gotAcceleration && gotRotationX && gotRotationY && gotRotationZ) {
+ final accelerations = _accel.split(',');
+ final capturePoint = CapturePoint(
+ type: captureType,
+ rotationX: _rotX,
+ rotationY: _rotY,
+ rotationZ: _rotZ,
+ accelerationX: accelerations[0],
+ accelerationY: accelerations[1],
+ accelerationZ: accelerations[2],
+ millisecondsSinceEpoch: DateTime.now().toUtc().millisecondsSinceEpoch,
+ );
+ emit(state.copyWith(lastCapturedPoint: capturePoint));
+ debugPrint("UPDATED POINT!");
+ // _writeToObjectBox(capturePoint: capturePoint);
+ gotRotationX = false;
+ gotRotationY = false;
+ gotRotationZ = false;
+ gotAcceleration = false;
+ }
+ });
+
+ device.cancelWhenDisconnected(dataCapturingSubscription);
+
+ await senseService.setNotifyValue(true);
+
+ emit(state.copyWith(status: XiaoConnectorStatus.capturing));
+ }
+
+ Future stopCapturing() async {
+ await dataCapturingSubscription.cancel();
+
+ emit(state.copyWith(status: XiaoConnectorStatus.connected));
+ }
+
+ void _writeToObjectBox({
+ required CapturePoint capturePoint,
+ }) {
+ _capturePointsBox.put(capturePoint);
+ }
+
+ void readFromObjectBox() {
+ final points = _capturePointsBox.getAll();
+ for (var i = 0; i < points.length; i++) {
+ final point = points[i];
+ debugPrint('Type: ${point.type}');
+ debugPrint('Id: ${point.id}');
+ debugPrint('RotX: ${point.rotationX}');
+ debugPrint('RotY: ${point.rotationY}');
+ debugPrint('RotZ: ${point.rotationZ}');
+ debugPrint('AccelX: ${point.accelerationX}');
+ debugPrint('AccelY: ${point.accelerationY}');
+ debugPrint('AccelZ: ${point.accelerationZ}');
+ debugPrint('Date: ${point.millisecondsSinceEpoch}');
+ }
+ }
+}
diff --git a/lib/xiao_connector/cubit/xiao_connector_state.dart b/lib/xiao_connector/cubit/xiao_connector_state.dart
new file mode 100644
index 0000000..1fce195
--- /dev/null
+++ b/lib/xiao_connector/cubit/xiao_connector_state.dart
@@ -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