Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
292 changes: 172 additions & 120 deletions open_wearable/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -27,6 +28,7 @@ void main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ThemeSettings()),
ChangeNotifierProvider(create: (context) => WearablesProvider()),
ChangeNotifierProvider(
create: (context) => FirmwareUpdateRequestProvider(),
Expand Down Expand Up @@ -71,115 +73,138 @@ class _MyAppState extends State<MyApp> 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<void>(
context: rootNavigatorKey
.currentContext!, // from navigator, not this widget
barrierDismissible: true,
builder: (_) => PlatformAlertDialog(
title: const Text('Firmware unsupported'),
content: getUnsupportedAlertText(evt),
actions: <Widget>[
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<void>(
context: rootNavigatorKey
.currentContext!, // from navigator, not this widget
barrierDismissible: true,
builder: (_) =>
PlatformAlertDialog(
title: const Text('Firmware unsupported'),
content: getUnsupportedAlertText(evt),
actions: <Widget>[
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<void>(
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<void>(
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<FirmwareUpdateRequestProvider>(
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>();
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>();
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<WearableConnector>();

final SensorRecorderProvider sensorRecorderProvider =
context.read<SensorRecorderProvider>();
context.read<SensorRecorderProvider>();
_autoConnector = BluetoothAutoConnector(
navStateGetter: () => rootNavigatorKey.currentState,
wearableManager: WearableManager(),
Expand Down Expand Up @@ -215,17 +240,17 @@ class _MyAppState extends State<MyApp> 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.',
);
}
}
Expand All @@ -242,31 +267,58 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {

@override
Widget build(BuildContext context) {
final themeNotifier = Provider.of<ThemeSettings>(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 <LocalizationsDelegate<dynamic>>[
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<dynamic>>[
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,
),
),
),
),
),
),
),
),
);
}
}
}
25 changes: 25 additions & 0 deletions open_wearable/lib/widgets/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -50,6 +52,7 @@ class _HomePageState extends State<HomePage> {
SensorPage(),
const AppsPage(),
];
_showStartupWizardOnFirstBoot();
}

@override
Expand Down Expand Up @@ -111,4 +114,26 @@ class _HomePageState extends State<HomePage> {
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);
});
});
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading