From 7d00d904de536523bdce413a27231dda5cb87f05 Mon Sep 17 00:00:00 2001 From: Codel1417 Date: Sun, 24 Nov 2024 19:21:28 -0500 Subject: [PATCH] Some work towards watch support --- lib/Backend/favorite_actions.dart | 2 +- lib/Backend/wear_bridge.dart | 77 ++++++++++++++---- lib/Frontend/WatchUI/actions.dart | 10 +++ lib/Frontend/WatchUI/main_menu.dart | 19 +++++ lib/Frontend/WatchUI/triggers.dart | 10 +++ lib/main.dart | 121 ++++++++++++++++++++++------ pubspec.lock | 16 +++- pubspec.yaml | 8 +- 8 files changed, 216 insertions(+), 47 deletions(-) create mode 100644 lib/Frontend/WatchUI/actions.dart create mode 100644 lib/Frontend/WatchUI/main_menu.dart create mode 100644 lib/Frontend/WatchUI/triggers.dart diff --git a/lib/Backend/favorite_actions.dart b/lib/Backend/favorite_actions.dart index 9e598a3f..c3cf2c02 100644 --- a/lib/Backend/favorite_actions.dart +++ b/lib/Backend/favorite_actions.dart @@ -73,6 +73,6 @@ class FavoriteActions extends _$FavoriteActions { await HiveProxy.clear(favoriteActionsBox); await HiveProxy.addAll(favoriteActionsBox, state); updateShortcuts(state, ref); - updateWearActions(state, ref); + ref.read(updateWearActionsProvider); } } diff --git a/lib/Backend/wear_bridge.dart b/lib/Backend/wear_bridge.dart index a19e4ebb..02e785a5 100644 --- a/lib/Backend/wear_bridge.dart +++ b/lib/Backend/wear_bridge.dart @@ -1,16 +1,17 @@ import 'dart:async'; import 'package:built_collection/built_collection.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tail_app/Backend/sensors.dart'; import 'package:watch_connectivity/watch_connectivity.dart'; import 'Definitions/Action/base_action.dart'; import 'action_registry.dart'; import 'favorite_actions.dart'; +part 'wear_bridge.freezed.dart'; part 'wear_bridge.g.dart'; final Logger _wearLogger = Logger('Wear'); @@ -30,7 +31,7 @@ Future initWear(InitWearRef ref) async { (event) => _wearLogger.info("Watch Context: $event"), ); - updateWearActions(ref.read(favoriteActionsProvider), ref); + ref.read(updateWearActionsProvider); } catch (e, s) { _wearLogger.severe("exception setting up Wear $e", e, s); } @@ -62,24 +63,72 @@ Future> applicationContext() { ); } -Future updateWearActions(BuiltList favoriteActions, Ref ref) async { +@Riverpod() +Future updateWearActions(UpdateWearActionsRef ref) async { try { - Iterable allActions = favoriteActions + Iterable allActions = ref + .read(favoriteActionsProvider) .map( (e) => ref.read(getActionFromUUIDProvider(e.actionUUID)), ) - .whereNotNull(); - final Map favoriteMap = Map.fromEntries(allActions.map((e) => MapEntry(e.uuid, e.name))); - final Map map = Map.fromEntries( - [ - MapEntry("actions", favoriteMap.values.join("_")), - MapEntry("uuid", favoriteMap.keys.join("_")), - ], - ); + .nonNulls; + //TODO: refresh when trigger toggled state changes + BuiltList triggers = ref.read(triggerListProvider); + final List favoriteMap = allActions.map((e) => WearActionData(uuid: e.uuid, name: e.name)).toList(); + final List triggersMap = triggers.map((e) => WearTriggerData(uuid: e.uuid, name: e.triggerDefinition!.name, enabled: e.enabled)).toList(); + + final WearData wearData = WearData(favoriteActions: favoriteMap, configuredTriggers: triggersMap); if (await _watch.isReachable) { - await _watch.sendMessage(map); + await _watch.updateApplicationContext(wearData.toJson()); } } catch (e, s) { _wearLogger.severe("Unable to send favorite actions to watch", e, s); } } + +@freezed +class WearData with _$WearData { + const factory WearData({ + required List favoriteActions, + required List configuredTriggers, + }) = _WearData; + + factory WearData.fromJson(Map json) => _$WearDataFromJson(json); +} + +@freezed +class WearTriggerData with _$WearTriggerData { + const factory WearTriggerData({ + required String name, + required String uuid, + required bool enabled, + }) = _WearTriggerData; + + factory WearTriggerData.fromJson(Map json) => _$WearTriggerDataFromJson(json); +} + +@freezed +class WearActionData with _$WearActionData { + const factory WearActionData({ + required String name, + required String uuid, + }) = _WearActionData; + + factory WearActionData.fromJson(Map json) => _$WearActionDataFromJson(json); +} + +@freezed +class WearCommand with _$WearCommand { + const factory WearCommand({ + required WearCommandType commandType, + required String uuid, + @Default(false) bool boolean, + }) = _WearCommand; + + factory WearCommand.fromJson(Map json) => _$WearCommandFromJson(json); +} + +enum WearCommandType { + runAction, + toggleTrigger, +} diff --git a/lib/Frontend/WatchUI/actions.dart b/lib/Frontend/WatchUI/actions.dart new file mode 100644 index 00000000..17e77807 --- /dev/null +++ b/lib/Frontend/WatchUI/actions.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class Actions extends StatelessWidget { + const Actions({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/Frontend/WatchUI/main_menu.dart b/lib/Frontend/WatchUI/main_menu.dart new file mode 100644 index 00000000..789ee702 --- /dev/null +++ b/lib/Frontend/WatchUI/main_menu.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../translation_string_definitions.dart'; + +class MainMenu extends StatelessWidget { + const MainMenu({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + FilledButton(onPressed: () {}, child: Text("Favorite Actions")), + FilledButton(onPressed: () {}, child: Text(homePage())), + FilledButton(onPressed: () {}, child: Text(triggersPage())), + FilledButton(onPressed: () {}, child: Text("Gear")), + ], + ); + } +} diff --git a/lib/Frontend/WatchUI/triggers.dart b/lib/Frontend/WatchUI/triggers.dart new file mode 100644 index 00000000..c1a7365c --- /dev/null +++ b/lib/Frontend/WatchUI/triggers.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class Triggers extends StatelessWidget { + const Triggers({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 1e9f4d80..77683345 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:install_referrer/install_referrer.dart'; import 'package:intl/intl.dart'; +import 'package:is_wear/is_wear.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -28,6 +29,7 @@ import 'Backend/favorite_actions.dart'; import 'Backend/logging_wrappers.dart'; import 'Backend/move_lists.dart'; import 'Backend/sensors.dart'; +import 'Backend/wear_bridge.dart'; import 'Frontend/Widgets/bt_app_state_controller.dart'; import 'Frontend/go_router_config.dart'; import 'Frontend/translation_string_definitions.dart'; @@ -80,32 +82,20 @@ Future getSentryEnvironment() async { return 'production'; } -Future main() async { - Logger.root.level = Level.ALL; - mainLogger.info("Begin"); - Logger.root.onRecord.listen((event) { - if (["GoRouter", "Dio"].contains(event.loggerName)) { - return; - } - if (event.level.value < 1000 && event.stackTrace == null) { - logarte.info(event.message, source: event.loggerName); - } else { - logarte.error(event.message, stackTrace: event.stackTrace); - } - }); - initFlutter(); +Future initMainApp() async { + //initialize the foreground service library + if (Platform.isAndroid) { + FlutterForegroundTask.initCommunicationPort(); + } + await startSentryApp(TailApp()); +} - initLocale(); - await initHive(); +Future startSentryApp(Widget child) async { mainLogger.fine("Init Sentry"); String environment = await getSentryEnvironment(); DynamicConfigInfo dynamicConfigInfo = await getDynamicConfigInfo(); mainLogger.info("Detected Environment: $environment"); - //initialize the foreground service library - if (Platform.isAndroid) { - FlutterForegroundTask.initCommunicationPort(); - } await SentryFlutter.init( (options) async { options @@ -126,16 +116,55 @@ Future main() async { // Init your App. // ignore: missing_provider_scope appRunner: () => runApp( - DefaultAssetBundle( - bundle: SentryAssetBundle(), - child: SentryScreenshotWidget( - child: TailApp(), - ), + SentryScreenshotWidget( + child: TailApp(), ), ), ); } +Future initWearApp() async { + await startSentryApp(TailAppWear()); +} + +Future main() async { + Logger.root.level = Level.ALL; + mainLogger.info("Begin"); + Logger.root.onRecord.listen((event) { + if (["GoRouter", "Dio"].contains(event.loggerName)) { + return; + } + if (event.level.value < 1000 && event.stackTrace == null) { + logarte.info(event.message, source: event.loggerName); + } else { + logarte.error(event.message, stackTrace: event.stackTrace); + } + }); + initFlutter(); + await initLocale(); + await initHive(); + + if (await isWear()) { + initWearApp(); + } else { + initMainApp(); + } +} + +Future isWear() async { + final IsWear isWearPlugin = IsWear(); + bool? result = await isWearPlugin.check().then((value) { + return value; + }).catchError((onError) { + return false; + }); + if (result == null) { + return false; + } else { + return result; + } +} + void initFlutter() { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized()..addObserver(WidgetBindingLogger()); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); // keeps the splash screen visible @@ -240,6 +269,7 @@ class TailApp extends ConsumerWidget { return WithForegroundTask( key: GlobalKey(debugLabel: "foregroundTask"), child: ProviderScope( + key: GlobalKey(debugLabel: "providerScope"), observers: [ RiverpodProviderObserver(), ], @@ -275,6 +305,45 @@ class TailApp extends ConsumerWidget { } } +class TailAppWear extends ConsumerWidget { + TailAppWear({super.key}) { + // Platform messages may fail, so we use a try/catch PlatformException. + mainLogger.info('Starting app'); + } + + // This widget is the root of your application. + @override + Widget build(BuildContext context, WidgetRef ref) { + return ProviderScope( + key: GlobalKey(debugLabel: "providerScope"), + observers: [ + RiverpodProviderObserver(), + ], + child: _EagerInitialization( + child: ValueListenableBuilder( + valueListenable: SentryHive.box(settings).listenable(keys: [appColor]), + builder: (BuildContext context, value, Widget? child) { + unawaited(setupSystemColor(context)); + Future(FlutterNativeSplash.remove); //remove the splash screen one frame later + Color color = Color(HiveProxy.getOrDefault(settings, appColor, defaultValue: appColorDefault)); + return MaterialApp.router( + title: title(), + color: color, + theme: buildTheme(Brightness.light, color), + darkTheme: buildTheme(Brightness.dark, color), + routerConfig: router, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + themeMode: ThemeMode.system, + debugShowCheckedModeBanner: false, + ); + }, + ), + ), + ); + } +} + ThemeData buildTheme(Brightness brightness, Color color) { if (brightness == Brightness.light) { return ThemeData( @@ -365,7 +434,7 @@ class _EagerInitialization extends ConsumerWidget { //ref.watch(favoriteActionsProvider); ref.watch(appShortcutsProvider); if (kDebugMode) { - //ref.watch(initWearProvider); + ref.watch(initWearProvider); } return child; } diff --git a/pubspec.lock b/pubspec.lock index ab1d44a6..d7cbefee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -981,6 +981,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + is_wear: + dependency: "direct main" + description: + name: is_wear + sha256: "5bf37738940d37ed1a77873d520e9bd2e57d6356802d6274fa52ba23c6625263" + url: "https://pub.dev" + source: hosted + version: "0.0.2+3" jni: dependency: transitive description: @@ -1258,10 +1266,10 @@ packages: dependency: "direct main" description: name: pdfrx - sha256: "3f9439e02f1bc82393225fe825867246b9a476ae5bf65b6a3e0a9ea4032aa8e9" + sha256: "9a0a9659ff2a0fd1f0057d66e936395f9d9d1043242e58f3f203134055cbe5bf" url: "https://pub.dev" source: hosted - version: "1.0.88" + version: "1.0.91" pedometer: dependency: "direct main" description: @@ -1960,10 +1968,10 @@ packages: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" web_socket: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cc60eace..5523ef4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,13 +45,17 @@ dependencies: url: https://github.com/undreeyyy/flutter_plugin_install_referrer ref: fd87e9b8f0d5ed909e929388244456f72b9b63c7 quick_actions: ^1.0.8 # puts favorites on the home screen - watch_connectivity: ^0.2.0 audioplayers: ^6.1.0 firebase_testlab_detector: ^1.0.2 platform: ^3.1.6 connectivity_plus: ^6.1.0 flutter_isolate: ^2.1.0 + # Watch + is_wear: ^0.0.2+3 + watch_connectivity: ^0.2.0 + + # Riverpod flutter_riverpod: ^2.6.1 riverpod_annotation: ^2.6.1 @@ -77,7 +81,7 @@ dependencies: flutter_widget_from_html_core: ^0.15.2 visibility_detector: ^0.4.0+2 # used on the tail blog widgets lottie: ^3.1.3 - pdfrx: ^1.0.88 + pdfrx: ^1.0.91 # Dio HTTP dio: ^5.7.0