From 24fc3598c89ae9f52a8e8863763246ff5cad9584 Mon Sep 17 00:00:00 2001 From: maddin Date: Sun, 28 Dec 2025 01:51:21 +0100 Subject: [PATCH] Startup Wizard and Theme Setting, --- open_wearable/lib/main.dart | 292 +++++++++++------- open_wearable/lib/widgets/home_page.dart | 25 ++ .../welcome_wizard/assets/encrypted.png | Bin 0 -> 26725 bytes .../welcome_wizard/assets/teco-logo.png | Bin 0 -> 2246 bytes .../welcome_wizard/startup_wizard.dart | 226 ++++++++++++++ .../welcome_wizard/theme_settings.dart | 47 +++ open_wearable/pubspec.lock | 86 +++--- 7 files changed, 513 insertions(+), 163 deletions(-) create mode 100644 open_wearable/lib/widgets/welcome_wizard/assets/encrypted.png create mode 100644 open_wearable/lib/widgets/welcome_wizard/assets/teco-logo.png create mode 100644 open_wearable/lib/widgets/welcome_wizard/startup_wizard.dart create mode 100644 open_wearable/lib/widgets/welcome_wizard/theme_settings.dart diff --git a/open_wearable/lib/main.dart b/open_wearable/lib/main.dart index 70bead86..a5aea512 100644 --- a/open_wearable/lib/main.dart +++ b/open_wearable/lib/main.dart @@ -10,6 +10,7 @@ import 'package:open_wearable/router.dart'; import 'package:open_wearable/view_models/sensor_recorder_provider.dart'; import 'package:open_wearable/widgets/app_banner.dart'; import 'package:open_wearable/widgets/global_app_banner_overlay.dart'; +import 'package:open_wearable/widgets/welcome_wizard/theme_settings.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -27,6 +28,7 @@ void main() async { runApp( MultiProvider( providers: [ + ChangeNotifierProvider(create: (context) => ThemeSettings()), ChangeNotifierProvider(create: (context) => WearablesProvider()), ChangeNotifierProvider( create: (context) => FirmwareUpdateRequestProvider(), @@ -71,115 +73,138 @@ class _MyAppState extends State with WidgetsBindingObserver { _unsupportedFirmwareSub = wearablesProvider.unsupportedFirmwareStream.listen((evt) { - // No async/await here. No widget context usage either. - final nav = rootNavigatorKey.currentState; - if (nav == null || !mounted) return; + // No async/await here. No widget context usage either. + final nav = rootNavigatorKey.currentState; + if (nav == null || !mounted) return; - // Push a dialog route via NavigatorState (no BuildContext from this widget) - nav.push( - DialogRoute( - context: rootNavigatorKey - .currentContext!, // from navigator, not this widget - barrierDismissible: true, - builder: (_) => PlatformAlertDialog( - title: const Text('Firmware unsupported'), - content: getUnsupportedAlertText(evt), - actions: [ - PlatformDialogAction( - cupertino: (_, __) => - CupertinoDialogActionData(isDefaultAction: true), - child: const Text('OK'), - // Close via navigator state; no widget context - onPressed: () => rootNavigatorKey.currentState?.pop(), - ), - ], - ), - ), - ); - }); + // Push a dialog route via NavigatorState (no BuildContext from this widget) + nav.push( + DialogRoute( + context: rootNavigatorKey + .currentContext!, // from navigator, not this widget + barrierDismissible: true, + builder: (_) => + PlatformAlertDialog( + title: const Text('Firmware unsupported'), + content: getUnsupportedAlertText(evt), + actions: [ + PlatformDialogAction( + cupertino: (_, __) => + CupertinoDialogActionData(isDefaultAction: true), + child: const Text('OK'), + // Close via navigator state; no widget context + onPressed: () => rootNavigatorKey.currentState?.pop(), + ), + ], + ), + ), + ); + }); - _wearableProvEventSub = wearablesProvider.wearableEventStream.listen((event) { - if (!mounted) return; + _wearableProvEventSub = + wearablesProvider.wearableEventStream.listen((event) { + if (!mounted) return; - // Handle firmware update available events with a dialog - if (event is NewFirmwareAvailableEvent) { - final nav = rootNavigatorKey.currentState; - if (nav == null || !mounted) return; + // Handle firmware update available events with a dialog + if (event is NewFirmwareAvailableEvent) { + final nav = rootNavigatorKey.currentState; + if (nav == null || !mounted) return; - nav.push( - DialogRoute( - context: rootNavigatorKey.currentContext!, - barrierDismissible: true, - builder: (dialogContext) => PlatformAlertDialog( - title: const Text('Firmware Update Available'), - content: Text( - 'A newer firmware version (${event.latestVersion}) is available. You are using version ${event.currentVersion}.', + nav.push( + DialogRoute( + context: rootNavigatorKey.currentContext!, + barrierDismissible: true, + builder: (dialogContext) => + PlatformAlertDialog( + title: const Text('Firmware Update Available'), + content: Text( + 'A newer firmware version (${event + .latestVersion}) is available. You are using version ${event + .currentVersion}.', + ), + actions: [ + PlatformDialogAction( + cupertino: (_, __) => CupertinoDialogActionData(), + child: const Text('Later'), + onPressed: () => rootNavigatorKey.currentState?.pop(), + ), + PlatformDialogAction( + cupertino: (_, __) => + CupertinoDialogActionData(isDefaultAction: true), + child: const Text('Update Now'), + onPressed: () { + // Set the selected peripheral for firmware update + final updateProvider = Provider.of< + FirmwareUpdateRequestProvider>( + rootNavigatorKey.currentContext!, + listen: false, + ); + updateProvider.setSelectedPeripheral( + event.wearable); + rootNavigatorKey.currentState?.pop(); + rootNavigatorKey.currentContext?.push('/fota'); + }, + ), + ], + ), ), - actions: [ - PlatformDialogAction( - cupertino: (_, __) => CupertinoDialogActionData(), - child: const Text('Later'), - onPressed: () => rootNavigatorKey.currentState?.pop(), - ), - PlatformDialogAction( - cupertino: (_, __) => - CupertinoDialogActionData(isDefaultAction: true), - child: const Text('Update Now'), - onPressed: () { - // Set the selected peripheral for firmware update - final updateProvider = Provider.of( - rootNavigatorKey.currentContext!, - listen: false, - ); - updateProvider.setSelectedPeripheral(event.wearable); - rootNavigatorKey.currentState?.pop(); - rootNavigatorKey.currentContext?.push('/fota'); - }, - ), - ], - ), - ), - ); - return; - } - - // Show a banner for other events using AppBannerController - final appBannerController = context.read(); - appBannerController.showBanner( - (id) { - late final Color backgroundColor; - if (event is WearableErrorEvent) { - backgroundColor = Theme.of(context).colorScheme.error; - } else { - backgroundColor = Theme.of(context).colorScheme.primary; + ); + return; } - late final Color textColor; - if (event is WearableErrorEvent) { - textColor = Theme.of(context).colorScheme.onError; - } else { - textColor = Theme.of(context).colorScheme.onPrimary; - } + // Show a banner for other events using AppBannerController + final appBannerController = context.read(); + appBannerController.showBanner( + (id) { + late final Color backgroundColor; + if (event is WearableErrorEvent) { + backgroundColor = Theme + .of(context) + .colorScheme + .error; + } else { + backgroundColor = Theme + .of(context) + .colorScheme + .primary; + } - return AppBanner( - content: Text( - event.description, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: textColor, - ), - ), - backgroundColor: backgroundColor, - key: ValueKey(id), + late final Color textColor; + if (event is WearableErrorEvent) { + textColor = Theme + .of(context) + .colorScheme + .onError; + } else { + textColor = Theme + .of(context) + .colorScheme + .onPrimary; + } + + return AppBanner( + content: Text( + event.description, + style: Theme + .of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: textColor, + ), + ), + backgroundColor: backgroundColor, + key: ValueKey(id), + ); + }, + duration: const Duration(seconds: 3), ); - }, - duration: const Duration(seconds: 3), - ); - }); + }); final WearableConnector connector = context.read(); final SensorRecorderProvider sensorRecorderProvider = - context.read(); + context.read(); _autoConnector = BluetoothAutoConnector( navStateGetter: () => rootNavigatorKey.currentState, wearableManager: WearableManager(), @@ -215,17 +240,17 @@ class _MyAppState extends State with WidgetsBindingObserver { if (evt is FirmwareTooOldEvent) { return const Text( 'The device has a firmware version that is too old and not supported by this app. ' - 'Please update the device firmware to ensure all features are working as expected.', + 'Please update the device firmware to ensure all features are working as expected.', ); } else if (evt is FirmwareTooNewEvent) { return const Text( 'The device has a firmware version that is too new and not supported by this app. ' - 'Please update the app to ensure all features are working as expected.', + 'Please update the app to ensure all features are working as expected.', ); } else { return const Text( 'The device has a firmware unsupported by this app. ' - 'Please update the app and Firmware to the newest version to ensure all features are working as expected.', + 'Please update the app and Firmware to the newest version to ensure all features are working as expected.', ); } } @@ -242,31 +267,58 @@ class _MyAppState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { + final themeNotifier = Provider.of(context); + return PlatformProvider( settings: PlatformSettingsData( iosUsesMaterialWidgets: true, ), - builder: (context) => PlatformTheme( - materialLightTheme: ThemeData( - useMaterial3: true, - colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), - cardTheme: const CardThemeData( - color: Colors.white, - elevation: 0, - ), - ), - builder: (context) => GlobalAppBannerOverlay( - child: PlatformApp.router( - routerConfig: router, - localizationsDelegates: const >[ - DefaultMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - DefaultCupertinoLocalizations.delegate, - ], - title: 'Open Wearable', + builder: (context) => + PlatformTheme( + materialLightTheme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + cardTheme: const CardThemeData( + color: Colors.white, + elevation: 0, + ), + ), + builder: (context) => + GlobalAppBannerOverlay( + child: PlatformApp.router( + routerConfig: router, + localizationsDelegates: const < + LocalizationsDelegate>[ + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + DefaultCupertinoLocalizations.delegate, + ], + title: 'Open Wearable', + + // ✅ Router variant => return MaterialAppRouterData + material: (_, __) => + MaterialAppRouterData( + themeMode: themeNotifier.themeMode, + theme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue), + cardTheme: const CardThemeData( + color: Colors.white, + elevation: 0, + ), + ), + darkTheme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + brightness: Brightness.dark, + ), + ), + ), + ), + ), ), - ), - ), ); } -} +} \ No newline at end of file diff --git a/open_wearable/lib/widgets/home_page.dart b/open_wearable/lib/widgets/home_page.dart index 54bb8ccf..b0209c9e 100644 --- a/open_wearable/lib/widgets/home_page.dart +++ b/open_wearable/lib/widgets/home_page.dart @@ -4,6 +4,8 @@ import 'package:open_wearable/apps/widgets/apps_page.dart'; import 'package:open_wearable/widgets/devices/devices_page.dart'; import 'package:open_wearable/widgets/sensors/configuration/sensor_configuration_view.dart'; import 'package:open_wearable/widgets/sensors/values/sensor_values_page.dart'; +import 'package:open_wearable/widgets/welcome_wizard/startup_wizard.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'sensors/sensor_page.dart'; @@ -50,6 +52,7 @@ class _HomePageState extends State { SensorPage(), const AppsPage(), ]; + _showStartupWizardOnFirstBoot(); } @override @@ -111,4 +114,26 @@ class _HomePageState extends State { items: items(context), ); } + + void _showStartupWizardOnFirstBoot() async { + final prefs = await SharedPreferences.getInstance(); + + // uncomment this line on release so infobox will only be shown once + //final shown = prefs.getBool('info_box_shown') ?? false; + final shown = false; + + if (!shown) { + // Delay to ensure context is ready + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StartupWizard(), + ).then((_) async { + // Mark as shown + await prefs.setBool('info_box_shown', true); + }); + }); + } + } } diff --git a/open_wearable/lib/widgets/welcome_wizard/assets/encrypted.png b/open_wearable/lib/widgets/welcome_wizard/assets/encrypted.png new file mode 100644 index 0000000000000000000000000000000000000000..ec12a43563f4bf1bb60373779b0bbb82d4e1e649 GIT binary patch literal 26725 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 bcakV=0X1l{jkEj literal 0 HcmV?d00001 diff --git a/open_wearable/lib/widgets/welcome_wizard/assets/teco-logo.png b/open_wearable/lib/widgets/welcome_wizard/assets/teco-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2dfa757cfc4ca21aa753e2f76888c4abb2764a3c GIT binary patch literal 2246 zcmds3{XY|kAD@k3EJT+Uri7@mvK~B*>y}aC%#KyVN@q$Qq8nmt)ph16t8kI+qRvy> zM1-vG;?AN64`FQ$6D9M|;nT#z26_kk3m={1O@>B0MGz` z3~rsD|9=d?>&PK+Spa}RK>+4^eA=D4QEI*g*;??$Rqs(haFOC`bO_pv$k_%#Jq2*W zu9ivmZ?cp4*ZJAO%-_U5%8o4vi5@Dmyg$6N^7Nz;><5e5y8~gl%{Ws!5NpJ_HE47H z(RQ3cDp{4lbvIc^?UOFd?Ot5lp%E67DezuuY78ZY{Q3tR&lm%-%&|b4I+1K~0^fTy zy1x#H2?TG~`wtzAfm~2`i8nK4h$6d-?he%ZID8h}oD5eG3fL(-lbnznVu6w1JAe&9 zgs)ptV>eGc@%buH@BL#{sB(; zM7ZK#Foh5Z>8WSp82IARUuVRVU4lKba6#(Ek->?ZM+!*W8Vp+xoCHlVCIg-0iqxQN zJ*@#z%y!&kS{%Pu;VDxKW{oZt)Hg;j*np~~-LbaLp&zWXwkE|_M1GEunUO-|Kyc+= zyoy>%_LOzWS0%^9u-PT2D^QGbzwR z({FLwgnfCmzA$C}WVhfJqaOuxv4Vi~55?{3P$Z^(G@V;>v|0(@$5TK_b`AG6*$;FV zm30nWzs7BzkWqg>!1!m;jcC74sTdgt&bJy+FFTrab|V@txZQb4Ju&j4X>tc|HY=#K zx~wL)_*i{jGGfnZOJX+kimFKj^GMIi<9zb+gk6p0r;@mQ z#`TL8E{V3zy@(!7>(WQTaN^Dchfs&kV@6B6zd&RWmN}4HEh@_c;Dov^y0Wcl;zmGv zNty|`@-m*1Ty0-K%tRR55SOo7A^{0$Ct3Dm5aV6%qsQuLhKy*D4z@0hb3uHdU&~g` z=ArG6Y7X4a+!I)SV);yD@{C1mjUnq?z?O(wN`e_Z(?Qr1+eDMX!&2Qqtxs}4{FE;a zs2al$sjGlZF@my;P;B1?^P`7rH$-M!!M<0%_^VFTLQ7A`wJ1Vq&+2u? zq^BA?NIY%p+_{X#qCR{*mo{%~Wl**8^{gD#?Te3_wy)?!b&QQanveNZG{mej#x$3- ztl>T$r_GcTK2~t?Ow^Tri^?ADh$iiAGYw9h+q9CTx8lt#{m=G>!5M> z^7BC+$G<#7AeVC613%fHq)csDQ?yw;zc%Oft?AWZ6SC-$5%&DM;kMd+cWV`fzto(M zoR^f785-$dIJ5maegC-ESrhnN7jt=oDmdLQeB`n|6mxjx@L)~F^-lJ;u;nvvn@nsZ zWn@O-%GR9R#_j1-uA)wsFt5C4Ai7~ulwNXBZ`BeMLf5iZ?>?^$BRqxJP%BkUX#^R1z8swr8&@y{9)k-A{0yhFS2a8hy6$g2fTL8UGfsmJ%qe%u#abULC? zb2eOae<}_pW68!G`JXlSOrEb~gmh2lZ+=UGW0d`@2y)$Yycz6o5PuZ@moMJ64`f7J z`#JdDZ@mr+o^-d!35hR0IQYzT@unm}Ra8riNFi6+xvhuJyxth$gR=kJDQ~2S0dG6m z*10QGK$Pj*cThxh!LMSz?;&tyqm@2dSNgkNsL3mF1SRq4+Cm4t=KTM3T>8d_>pgPJ z4gxbhp1hCdy1v?y1A8_w7eR$6^kg$I@6Tx2$Q(c}Y9GJjR!py9dKpPK1in<6E%avf zEWN#LO{-v!UYjV~Ey!|ukH6XMj_$5kHE4BY89$-1x3xErf3ECa^<1b;Ug_07^A=y3 z!~I$Os;XA7gLH;+@hL)M?NPLr{T2HUlXDN2Vt(|$-;%2b0Dy1b3Mzg!K)uxjtp6u~ NfWtu;?jh3Ue*qS`?yvv= literal 0 HcmV?d00001 diff --git a/open_wearable/lib/widgets/welcome_wizard/startup_wizard.dart b/open_wearable/lib/widgets/welcome_wizard/startup_wizard.dart new file mode 100644 index 00000000..34eb3180 --- /dev/null +++ b/open_wearable/lib/widgets/welcome_wizard/startup_wizard.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import 'package:open_wearable/widgets/welcome_wizard/theme_settings.dart'; +import 'package:provider/provider.dart'; + +class StartupWizard extends StatefulWidget { + const StartupWizard({Key? key}) : super(key: key); + + @override + State createState() => _StartupWizardDialogState(); +} + +class _StartupWizardDialogState extends State { + int _currentStep = 0; + + late final List steps = [ + // Step 1: Welcome Screen + Column( + children: [ + // TECO Logo Placeholder + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Image.asset( + 'lib/widgets/welcome_wizard/assets/teco-logo.png', + width: 40, + height: 40, + fit: BoxFit.contain, + ), + ), + const SizedBox(height: 24), + const Text( + 'Welcome to OpenWearable', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 18), + const Text( + "Get ready to connect and use your wearable device with advanced posture and heart tracking. This quick setup will guide you through everything you need.", + style: TextStyle(fontSize: 14), + textAlign: TextAlign.center, + ), + ], + ), + // Step 2: Theme Selection + Column( + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Icon(Icons.palette_outlined, size: 36, color: Colors.grey[700]), + ), + const SizedBox(height: 24), + const Text( + 'Choose Your Theme', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + const SizedBox(height: 16), + const Text( + "Pick your favorite look. You can always change this later in the settings.", + style: TextStyle(fontSize: 14), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + OutlinedButton.icon( + icon: const Icon(Icons.light_mode), + label: const Text('Light'), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + Provider.of(context, listen: false).setTheme(ThemeMode.light); + }, + ), + const SizedBox(width: 18), + ElevatedButton.icon( + icon: const Icon(Icons.dark_mode), + label: const Text('Dark'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + Provider.of(context, listen: false).setTheme(ThemeMode.dark); + }, + ), + ], + ), + ], + ), + // Step 3: Data is Info Page + Column( + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + border: Border.all(), + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Image.asset( + 'lib/widgets/welcome_wizard/assets/encrypted.png', + width: 40, + height: 40, + fit: BoxFit.contain, + ), + ), + const SizedBox(height: 24), + const Text( + 'Your Data, Your Device, 100% Secure', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 24), + const Text( + "We respect your privacy. All data is stored locally on your device never shared, and never uploaded to the cloud. You're always in control.", + style: TextStyle(fontSize: 14), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ], + ) + ]; + + void _next() { + if (_currentStep < steps.length - 1) { + setState(() => _currentStep++); + } else { + Navigator.of(context).pop(); + } + } + + void _back() { + if (_currentStep > 0) { + setState(() => _currentStep--); + } + } + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: SizedBox( + width: 320, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + steps[_currentStep], + const SizedBox(height: 28), + // Progress dots + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + steps.length, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: _currentStep == index ? 14 : 8, + height: 8, + decoration: BoxDecoration( + color: _currentStep == index + ? Theme.of(context).colorScheme.primary + : Colors.grey[400], + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + const SizedBox(height: 28), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (_currentStep > 0) + OutlinedButton( + onPressed: _back, + child: const Text('Back'), + ) + else + const SizedBox(width: 80), + ElevatedButton( + onPressed: _next, + child: Text( + _currentStep == steps.length - 1 ? 'Finish' : 'Next', + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/open_wearable/lib/widgets/welcome_wizard/theme_settings.dart b/open_wearable/lib/widgets/welcome_wizard/theme_settings.dart new file mode 100644 index 00000000..8922561d --- /dev/null +++ b/open_wearable/lib/widgets/welcome_wizard/theme_settings.dart @@ -0,0 +1,47 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ThemeSettings extends ChangeNotifier { + ThemeMode _themeMode = ThemeMode.light; + ThemeMode get themeMode => _themeMode; + + static const String themeKey = 'theme_mode'; + + ThemeSettings() { + _loadThemeFromPrefs(); + } + + void setTheme(ThemeMode mode) async { + _themeMode = mode; + notifyListeners(); + _saveThemeToPrefs(); + } + + Future _loadThemeFromPrefs() async { + final prefs = await SharedPreferences.getInstance(); + final themeString = prefs.getString(themeKey); + + if (themeString == 'dark') { + _themeMode = ThemeMode.dark; + } else if (themeString == 'light') { + _themeMode = ThemeMode.light; + } else if (themeString == 'system') { + _themeMode = ThemeMode.system; + } // else default to light + notifyListeners(); + } + + Future _saveThemeToPrefs() async { + final prefs = await SharedPreferences.getInstance(); + String themeString; + if (_themeMode == ThemeMode.dark) { + themeString = 'dark'; + } else if (_themeMode == ThemeMode.light) { + themeString = 'light'; + } else { + themeString = 'system'; + } + await prefs.setString(themeKey, themeString); + } +} \ No newline at end of file diff --git a/open_wearable/pubspec.lock b/open_wearable/pubspec.lock index 0458b180..b16c18f9 100644 --- a/open_wearable/pubspec.lock +++ b/open_wearable/pubspec.lock @@ -157,34 +157,34 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "7872545770c277236fd32b022767576c562ba28366204ff1a5628853cf8f2200" + sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde url: "https://pub.dev" source: hosted - version: "10.3.7" + version: "10.3.8" file_selector: dependency: "direct main" description: name: file_selector - sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a + sha256: "5f1d15a7f17115038f433d1b0ea57513cc9e29a9d5338d166cb0bef3fa90a7a0" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.0.4" file_selector_android: dependency: transitive description: name: file_selector_android - sha256: "51e8fd0446de75e4b62c065b76db2210c704562d072339d333bd89c57a7f8a7c" + sha256: "1ce58b609289551f8ec07265476720e77d19764339cc1d8e4df3c4d34dac6499" url: "https://pub.dev" source: hosted - version: "0.5.2+4" + version: "0.5.1+17" file_selector_ios: dependency: transitive description: name: file_selector_ios - sha256: "628ec99afd8bb40620b4c8707d5fd5fc9e89d83e9b0b327d471fe5f7bc5fc33f" + sha256: fe9f52123af16bba4ad65bd7e03defbbb4b172a38a8e6aaa2a869a0c56a5f5fb url: "https://pub.dev" source: hosted - version: "0.5.3+4" + version: "0.5.3+2" file_selector_linux: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" url: "https://pub.dev" source: hosted - version: "0.9.5" + version: "0.9.4+4" file_selector_platform_interface: dependency: transitive description: @@ -237,10 +237,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "7ca9a40f4eb85949190e54087be8b4d6ac09dc4c54238d782a34cf1f7c011de9" + sha256: d3f82f4a38e33ba23d05a08ff304d7d8b22d2a59a5503f20bd802966e915db89 url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -290,10 +290,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 url: "https://pub.dev" source: hosted - version: "2.0.33" + version: "2.0.31" flutter_staggered_grid_view: dependency: "direct main" description: @@ -372,26 +372,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -444,10 +444,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -564,18 +564,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" url: "https://pub.dev" source: hosted - version: "2.2.22" + version: "2.2.19" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -732,18 +732,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b" + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e url: "https://pub.dev" source: hosted - version: "2.4.17" + version: "2.4.13" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -825,10 +825,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.4" tuple: dependency: transitive description: @@ -865,18 +865,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" url: "https://pub.dev" source: hosted - version: "6.3.28" + version: "6.3.20" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 url: "https://pub.dev" source: hosted - version: "6.3.6" + version: "6.3.4" url_launcher_linux: dependency: transitive description: @@ -889,10 +889,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f url: "https://pub.dev" source: hosted - version: "3.2.5" + version: "3.2.3" url_launcher_platform_interface: dependency: transitive description: @@ -950,7 +950,7 @@ packages: source: hosted version: "1.1.19" vector_math: - dependency: transitive + dependency: "direct overridden" description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b @@ -961,10 +961,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.0.0" web: dependency: transitive description: @@ -998,5 +998,5 @@ packages: source: hosted version: "6.6.1" sdks: - dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.32.0"