From f8a9844f99bc022512fce6e960565260e9343ea6 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 29 Oct 2024 16:03:49 +0000 Subject: [PATCH 1/2] Switching from rootNavKey.currentContext! to ProviderContainer --- app/integration_test/support/util.dart | 9 ++--- app/lib/config/notifications/init.dart | 40 ++++++++++++------- app/lib/config/notifications/util.dart | 7 ++-- app/lib/config/setup.dart | 3 ++ .../features/calendar_sync/calendar_sync.dart | 9 ++--- app/lib/main.dart | 8 +++- .../shell_routers/chat_shell_router.dart | 27 ++++++------- app/lib/router/utils.dart | 9 ++--- 8 files changed, 60 insertions(+), 52 deletions(-) diff --git a/app/integration_test/support/util.dart b/app/integration_test/support/util.dart index 21d431543f4e..0374c2029841 100644 --- a/app/integration_test/support/util.dart +++ b/app/integration_test/support/util.dart @@ -1,14 +1,13 @@ import 'dart:io'; -import 'package:acter/common/extensions/acter_build_context.dart'; import 'package:acter/common/utils/constants.dart'; import 'package:acter/common/widgets/spaces/select_space_form_field.dart'; +import 'package:acter/config/setup.dart'; import 'package:acter/features/home/data/keys.dart'; import 'package:acter/features/labs/model/labs_features.dart'; import 'package:acter/features/labs/providers/labs_providers.dart'; import 'package:acter/features/search/model/keys.dart'; import 'package:acter/features/settings/widgets/settings_menu.dart'; -import 'package:acter/router/router.dart'; import 'package:convenient_test_dev/convenient_test_dev.dart'; import 'package:image_picker/image_picker.dart'; import 'package:flutter/material.dart'; @@ -62,7 +61,7 @@ extension ActerUtil on ConvenientTest { } Future ensureLabEnabled(LabsFeature feat) async { - if (!rootNavKey.currentContext!.read(isActiveProvider(feat))) { + if (!mainProviderContainer.read(isActiveProvider(feat))) { // ensure we do actually have access to the main nav. await find.byKey(Keys.mainNav).should(findsOneWidget); final quickJumpKey = find.byKey(MainNavKeys.quickJump); @@ -80,7 +79,7 @@ extension ActerUtil on ConvenientTest { final confirmKey = find.byKey(Key('labs-${feat.name}')); await confirmKey.should(findsOneWidget); // let's read again - if (!rootNavKey.currentContext!.read(isActiveProvider(feat))) { + if (!mainProviderContainer.read(isActiveProvider(feat))) { await confirmKey.tap(); } @@ -88,7 +87,7 @@ extension ActerUtil on ConvenientTest { // ensure we are active assert( - rootNavKey.currentContext!.read(isActiveProvider(feat)), + mainProviderContainer.read(isActiveProvider(feat)), 'Could not activate $feat', ); } diff --git a/app/lib/config/notifications/init.dart b/app/lib/config/notifications/init.dart index a2678b82634b..72491245dc5f 100644 --- a/app/lib/config/notifications/init.dart +++ b/app/lib/config/notifications/init.dart @@ -1,11 +1,11 @@ import 'dart:io'; -import 'package:acter/common/extensions/acter_build_context.dart'; import 'package:acter/common/providers/app_state_provider.dart'; import 'package:acter/common/providers/sdk_provider.dart'; import 'package:acter/config/env.g.dart'; import 'package:acter/config/notifications/firebase_options.dart'; import 'package:acter/config/notifications/util.dart'; +import 'package:acter/config/setup.dart'; import 'package:acter/features/labs/model/labs_features.dart'; import 'package:acter/features/labs/providers/labs_providers.dart'; import 'package:acter/router/router.dart'; @@ -94,22 +94,38 @@ Future setupPushNotifications( } bool _handleMessageTap(Map data) { - _log.info('Notification was tapped. Data: \n $data'); + final context = rootNavKey.currentContext; + if (context == null) { + // no context "et", delay by 300ms and try again; + Future.delayed( + const Duration(milliseconds: 300), + () => _handleMessageTap(data), + ); + return false; + } + return _handleMessageTapForContext(context, data); +} + +bool _handleMessageTapForContext( + BuildContext context, + Map data, +) { + _log.info('Notification was tapped. Data: \n $data'); try { final uri = data['payload'] as String?; if (uri != null) { _log.info('Uri found $uri'); if (isCurrentRoute(uri)) { // ensure we reload - rootNavKey.currentContext!.replace(uri); + context.replace(uri); } else { _log.info('Different page, routing'); if (shouldReplaceCurrentRoute(uri)) { // this is a chat-room page, replace this to allow for // a smother "back"-navigation story - rootNavKey.currentContext!.pushReplacement(uri); + context.pushReplacement(uri); } else { - rootNavKey.currentContext!.push(uri); + context.push(uri); } } return true; @@ -123,7 +139,7 @@ bool _handleMessageTap(Map data) { return false; } // fallback support - rootNavKey.currentContext!.push( + context.push( makeForward(roomId: roomId, deviceId: deviceId, eventId: eventId), ); } catch (e, s) { @@ -136,7 +152,7 @@ bool _handleMessageTap(Map data) { bool _isEnabled() { try { // ignore: use_build_context_synchronously - if (!rootNavKey.currentContext! + if (!mainProviderContainer .read(isActiveProvider(LabsFeature.mobilePushNotifications))) { _log.info( 'Showing push notifications has been disabled on this device. Ignoring', @@ -153,7 +169,7 @@ bool _shouldShow(String url) { // we ignore if we are in foreground and looking at that URL if (isCurrentRoute(url) && // ignore: use_build_context_synchronously - rootNavKey.currentContext!.read(isAppInForeground)) { + mainProviderContainer.read(isAppInForeground)) { return false; } return true; @@ -164,13 +180,7 @@ Future> _genCurrentClients() async { 'Received the update information for the token. Updating all clients.', ); List clients = []; - // ignore: use_build_context_synchronously - final currentContext = rootNavKey.currentContext; - if (currentContext == null) { - _log.warning('No currentContext found. skipping setting of new token'); - return clients; - } - final sdk = await currentContext.read(sdkProvider.future); + final sdk = await mainProviderContainer.read(sdkProvider.future); for (final client in sdk.clients) { final deviceId = client.deviceId().toString(); diff --git a/app/lib/config/notifications/util.dart b/app/lib/config/notifications/util.dart index 12da75649b0f..c625e64fa78b 100644 --- a/app/lib/config/notifications/util.dart +++ b/app/lib/config/notifications/util.dart @@ -1,6 +1,5 @@ -import 'package:acter/common/extensions/acter_build_context.dart'; +import 'package:acter/config/setup.dart'; import 'package:acter/router/providers/router_providers.dart'; -import 'package:acter/router/router.dart'; import 'package:acter/router/utils.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -18,7 +17,7 @@ Future setRejected(String deviceId, bool value) async { } bool isCurrentRoute(String uri) { - final currentUri = rootNavKey.currentContext!.read(currentRoutingLocation); + final currentUri = mainProviderContainer.read(currentRoutingLocation); return currentUri == uri; } @@ -27,7 +26,7 @@ bool shouldReplaceCurrentRoute(String uri) { return false; } - final currentUri = rootNavKey.currentContext!.read(currentRoutingLocation); + final currentUri = mainProviderContainer.read(currentRoutingLocation); return currentUri.startsWith(chatRoomUriMatcher); } diff --git a/app/lib/config/setup.dart b/app/lib/config/setup.dart index f81838de47ce..cfafa30e5f24 100644 --- a/app/lib/config/setup.dart +++ b/app/lib/config/setup.dart @@ -2,12 +2,15 @@ import 'dart:io'; import 'package:acter/config/env.g.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; const userAgent = '${Env.rageshakeAppName}/${Env.rageshakeAppName}'; final defaultLogSetting = Platform.environment.containsKey(rustLogKey) ? Platform.environment[rustLogKey] as String : Env.defaultRustLog; +final mainProviderContainer = ProviderContainer(); + void configSetup() { // Pass the configuration to the SDK plugin ActerSdk.setup( diff --git a/app/lib/features/calendar_sync/calendar_sync.dart b/app/lib/features/calendar_sync/calendar_sync.dart index 6ed121af645a..8f3c844d6a8a 100644 --- a/app/lib/features/calendar_sync/calendar_sync.dart +++ b/app/lib/features/calendar_sync/calendar_sync.dart @@ -1,12 +1,11 @@ import 'dart:async'; import 'dart:io'; -import 'package:acter/common/extensions/acter_build_context.dart'; import 'package:acter/common/themes/colors/color_scheme.dart'; +import 'package:acter/config/setup.dart'; import 'package:acter/features/calendar_sync/providers/events_to_sync_provider.dart'; import 'package:acter/features/labs/model/labs_features.dart'; import 'package:acter/features/labs/providers/labs_providers.dart'; -import 'package:acter/router/router.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:device_calendar/device_calendar.dart'; @@ -32,7 +31,7 @@ ProviderSubscription>>? _subscription; Future _isEnabled() async { try { - return (await rootNavKey.currentContext! + return (await mainProviderContainer .read(asyncIsActiveProvider(LabsFeature.deviceCalendarSync).future)); } catch (e, s) { _log.severe('Reading current context failed', e, s); @@ -87,9 +86,7 @@ Future initCalendarSync({bool ignoreRejection = false}) async { // clear if it existed before _subscription?.close(); // start listening - _subscription = - ProviderScope.containerOf(rootNavKey.currentContext!, listen: true) - .listen( + _subscription = mainProviderContainer.listen( eventsToSyncProvider, (prev, next) async { final events = next.valueOrNull; diff --git a/app/lib/main.dart b/app/lib/main.dart index 6cdfcbbdb23c..853e1ec58dc2 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -69,6 +69,10 @@ Future _startAppInner(Widget app, bool withSentry) async { app = DesktopSupport(child: app); } + // use the globally defined ProviderContainer + final wrappedApp = + UncontrolledProviderScope(container: mainProviderContainer, child: app); + if (withSentry) { await SentryFlutter.init( (options) { @@ -81,10 +85,10 @@ Future _startAppInner(Widget app, bool withSentry) async { // and prevent reporting otherwise. options.beforeSend = sentryBeforeSend; }, - appRunner: () => runApp(app), + appRunner: () => runApp(wrappedApp), ); } else { - runApp(app); + runApp(wrappedApp); } } diff --git a/app/lib/router/shell_routers/chat_shell_router.dart b/app/lib/router/shell_routers/chat_shell_router.dart index ad4b62693caf..5503216bbccc 100644 --- a/app/lib/router/shell_routers/chat_shell_router.dart +++ b/app/lib/router/shell_routers/chat_shell_router.dart @@ -1,7 +1,7 @@ -import 'package:acter/common/extensions/acter_build_context.dart'; import 'package:acter/common/extensions/options.dart'; import 'package:acter/common/providers/chat_providers.dart'; import 'package:acter/common/utils/routes.dart'; +import 'package:acter/config/setup.dart'; import 'package:acter/features/chat/pages/room_page.dart'; import 'package:acter/features/chat/pages/room_profile_page.dart'; import 'package:acter/features/chat/widgets/chat_layout_builder.dart'; @@ -19,8 +19,7 @@ import 'package:go_router/go_router.dart'; /// the chat-ng feature. Widget _chatLayoutBuilder({Widget? centerChild, Widget? expandedChild}) { final isChatNg = - rootNavKey.currentContext?.read(isActiveProvider(LabsFeature.chatNG)) == - true; + mainProviderContainer.read(isActiveProvider(LabsFeature.chatNG)) == true; return isChatNg ? ChatLayoutBuilder( roomListWidgetBuilder: (s) => RoomsListNGWidget(onSelected: s), @@ -39,9 +38,7 @@ final chatShellRoutes = [ path: Routes.chat.route, redirect: authGuardRedirect, pageBuilder: (context, state) { - rootNavKey.currentContext - ?.read(selectedChatIdProvider.notifier) - .select(null); + mainProviderContainer.read(selectedChatIdProvider.notifier).select(null); return NoTransitionPage( key: state.pageKey, child: _chatLayoutBuilder(), @@ -53,13 +50,13 @@ final chatShellRoutes = [ path: Routes.chatroom.route, redirect: authGuardRedirect, pageBuilder: (context, state) { - final isChatNg = rootNavKey.currentContext - ?.read(isActiveProvider(LabsFeature.chatNG)) == - true; + final isChatNg = + mainProviderContainer.read(isActiveProvider(LabsFeature.chatNG)) == + true; final roomId = state.pathParameters['roomId']!; - rootNavKey.currentContext - ?.read(selectedChatIdProvider.notifier) + mainProviderContainer + .read(selectedChatIdProvider.notifier) .select(roomId); return NoTransitionPage( key: state.pageKey, @@ -82,8 +79,8 @@ final chatShellRoutes = [ pageBuilder: (context, state) { final roomId = state.pathParameters['roomId'] .expect('chatProfile route needs roomId as path param'); - rootNavKey.currentContext - ?.read(selectedChatIdProvider.notifier) + mainProviderContainer + .read(selectedChatIdProvider.notifier) .select(roomId); return NoTransitionPage( key: state.pageKey, @@ -101,8 +98,8 @@ final chatShellRoutes = [ pageBuilder: (context, state) { final roomId = state.pathParameters['roomId'] .expect('chatSettingsVisibility route needs roomId as path param'); - rootNavKey.currentContext - ?.read(selectedChatIdProvider.notifier) + mainProviderContainer + .read(selectedChatIdProvider.notifier) .select(roomId); return NoTransitionPage( key: state.pageKey, diff --git a/app/lib/router/utils.dart b/app/lib/router/utils.dart index 3efc61f0deca..1d636f618b44 100644 --- a/app/lib/router/utils.dart +++ b/app/lib/router/utils.dart @@ -1,7 +1,7 @@ -import 'package:acter/common/extensions/acter_build_context.dart'; import 'package:acter/common/providers/chat_providers.dart'; import 'package:acter/common/utils/routes.dart'; import 'package:acter/config/app_shell.dart'; +import 'package:acter/config/setup.dart'; import 'package:acter/router/providers/router_providers.dart'; import 'package:acter/router/router.dart'; import 'package:flutter/material.dart'; @@ -54,8 +54,7 @@ final chatRoomUriMatcher = RegExp('/chat/.+'); /// helper to figure out how to route to the specific chat room void goToChat(BuildContext localContext, String roomId) { - final context = rootNavKey.currentContext!; - final currentUri = context.read(currentRoutingLocation); + final currentUri = mainProviderContainer.read(currentRoutingLocation); if (!currentUri.startsWith(chatRoomUriMatcher)) { // we are not in a chat room. just a regular push routing // will do @@ -69,13 +68,13 @@ void goToChat(BuildContext localContext, String roomId) { } // we are in a chat page - if (roomId == rootNavKey.currentContext!.read(selectedChatIdProvider)) { + if (roomId == mainProviderContainer.read(selectedChatIdProvider)) { // we are on the same page, nothing to be done return; } // we are on a different chat page. Push replace the current screen - context.pushReplacementNamed( + (rootNavKey.currentContext ?? localContext).pushReplacementNamed( Routes.chatroom.name, pathParameters: {'roomId': roomId}, ); From 05c046b862cde9c543452aa38d7e8c015a8b92da Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 29 Oct 2024 16:30:12 +0000 Subject: [PATCH 2/2] Reporting notification stuff to Sentry --- packages/acter_notifify/lib/acter_notifify.dart | 2 ++ packages/acter_notifify/lib/matrix.dart | 2 ++ packages/acter_notifify/lib/ntfy.dart | 3 +++ packages/acter_notifify/lib/platform/android.dart | 4 ++++ packages/acter_notifify/lib/push.dart | 3 +++ packages/acter_notifify/pubspec.yaml | 1 + 6 files changed, 15 insertions(+) diff --git a/packages/acter_notifify/lib/acter_notifify.dart b/packages/acter_notifify/lib/acter_notifify.dart index 2c366e1a491a..0171e29ebaae 100644 --- a/packages/acter_notifify/lib/acter_notifify.dart +++ b/packages/acter_notifify/lib/acter_notifify.dart @@ -11,6 +11,7 @@ import 'package:acter_notifify/util.dart'; import 'package:acter_notifify/platform/windows.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:logging/logging.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; final _log = Logger('a3::notifify::acter'); @@ -87,6 +88,7 @@ Future initializeNotifify({ ); } catch (error, stack) { final deviceId = client.deviceId().toString(); + Sentry.captureException(error, stackTrace: stack); _log.severe('Failed to setup ntfy for $deviceId', error, stack); } } diff --git a/packages/acter_notifify/lib/matrix.dart b/packages/acter_notifify/lib/matrix.dart index c1a74370b8e8..23aa63dbcd07 100644 --- a/packages/acter_notifify/lib/matrix.dart +++ b/packages/acter_notifify/lib/matrix.dart @@ -14,6 +14,7 @@ import 'package:acter_notifify/platform/windows.dart'; import 'package:convert/convert.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:logging/logging.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; final _log = Logger('a3::notifify::matrix'); int id = 0; @@ -47,6 +48,7 @@ Future handleMatrixMessage( return true; } catch (e, s) { _log.severe('Parsing Notification failed: $message', e, s); + Sentry.captureException(e, stackTrace: s); } return false; } diff --git a/packages/acter_notifify/lib/ntfy.dart b/packages/acter_notifify/lib/ntfy.dart index 81f38fa84ab5..2915e90e1093 100644 --- a/packages/acter_notifify/lib/ntfy.dart +++ b/packages/acter_notifify/lib/ntfy.dart @@ -6,6 +6,7 @@ import 'package:acter_notifify/acter_notifify.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:acter_notifify/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; @@ -58,6 +59,7 @@ Future setupNtfyNotificationsForDevice( ); if (rs.data == null) { _log.severe('Connecting to ntfy server failed: $rs'); + Sentry.captureMessage('Connecting to ntfy server failed: $rs'); return false; } _subscriptions[token] = rs.data!.stream @@ -83,6 +85,7 @@ Future setupNtfyNotificationsForDevice( ); } catch (error, stack) { _log.severe('Failed to show push notification $event', error, stack); + Sentry.captureException(error, stackTrace: stack); } }); return true; diff --git a/packages/acter_notifify/lib/platform/android.dart b/packages/acter_notifify/lib/platform/android.dart index 708774ce57ec..feda9e4c384d 100644 --- a/packages/acter_notifify/lib/platform/android.dart +++ b/packages/acter_notifify/lib/platform/android.dart @@ -5,6 +5,7 @@ import 'package:acter_notifify/local.dart'; import 'package:acter_notifify/matrix.dart'; import 'package:acter_notifify/util.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:logging/logging.dart'; final _log = Logger('a3::notifify::android'); @@ -24,6 +25,7 @@ Future _fetchImage( return ByteArrayAndroidBitmap(image.asTypedList()); } catch (e, s) { _log.severe('fetching image data failed', e, s); + Sentry.captureException(e, stackTrace: s); } } return null; @@ -41,6 +43,7 @@ Future _makeSenderPerson(NotificationItem notification) async { ); } catch (e, s) { _log.severe('fetching image data failed', e, s); + Sentry.captureException(e, stackTrace: s); } } return Person(key: sender.userId(), name: sender.displayName()); @@ -56,6 +59,7 @@ Future _fetchRoomAvatar( return ByteArrayAndroidBitmap(image.asTypedList()); } catch (e, s) { _log.severe('fetching room avatar failed', e, s); + Sentry.captureException(e, stackTrace: s); } } return null; diff --git a/packages/acter_notifify/lib/push.dart b/packages/acter_notifify/lib/push.dart index 645f53d72840..6178c3a2a78e 100644 --- a/packages/acter_notifify/lib/push.dart +++ b/packages/acter_notifify/lib/push.dart @@ -6,6 +6,7 @@ import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:acter_notifify/acter_notifify.dart'; import 'package:acter_notifify/matrix.dart'; import 'package:logging/logging.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:push/push.dart'; final _log = Logger('a3::notifify::push'); @@ -77,12 +78,14 @@ Future initializePush({ pushServerUrl: pushServerUrl); } catch (error, st) { _log.severe('Setting token for $deviceId failed', error, st); + Sentry.captureException(error, stackTrace: st); } } }); } catch (e, s) { // this fails on hot-reload and in integration tests... if so, ignore for now _log.severe('Push initialization error', e, s); + Sentry.captureException(e, stackTrace: s); } } diff --git a/packages/acter_notifify/pubspec.yaml b/packages/acter_notifify/pubspec.yaml index 107ab1b7b4ff..5c89d00246b9 100644 --- a/packages/acter_notifify/pubspec.yaml +++ b/packages/acter_notifify/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: dio: ^5.5.0+1 windows_notification: ^1.2.0 app_badge_plus: ^1.1.5 + sentry: any dev_dependencies: flutter_test: