diff --git a/CHANGELOG.md b/CHANGELOG.md index 63725911..280cc758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.31 + +- Fix: Issue with background notifications on Android + ## 0.2.30 - Fix: Changed minimum threshold for the user discovery to 3 diff --git a/lib/main.dart b/lib/main.dart index a4ed4ed5..619cb22e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,7 +76,7 @@ void main() async { unawaited(StartupGuard.markAppStartup()); var storageError = await twonlyMinimumInitialization(); - await initFCMService(); + await FcmNotificationService.initStartup(); var userExists = false; @@ -109,6 +109,8 @@ void main() async { unawaited(initFileDownloader()); if (userExists) { + unawaited(FcmNotificationService.initAfterUserLoaded()); + if (userService.currentUser.allowErrorTrackingViaSentry) { AppState.allowErrorTrackingViaSentry = true; await SentryFlutter.init( diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 65cc226b..2e2bbc61 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -21,7 +21,8 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' as server; +import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' + as server; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart'; import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart'; import 'package:twonly/src/services/api/mediafiles/download.api.dart'; @@ -65,13 +66,15 @@ class ApiService { Stream get onPlanUpdated => _planUpdateController.stream; final _connectionStateController = StreamController.broadcast(); - Stream get onConnectionStateUpdated => _connectionStateController.stream; + Stream get onConnectionStateUpdated => + _connectionStateController.stream; final _appOutdatedController = StreamController.broadcast(); Stream get onAppOutdated => _appOutdatedController.stream; final _newDeviceRegisteredController = StreamController.broadcast(); - Stream get onNewDeviceRegistered => _newDeviceRegisteredController.stream; + Stream get onNewDeviceRegistered => + _newDeviceRegisteredController.stream; bool appIsOutdated = false; bool isAuthenticated = false; @@ -80,7 +83,8 @@ class ApiService { Timer? reconnectionTimer; int _reconnectionDelay = 5; - final HashMap> _pendingRequests = HashMap(); + final HashMap> _pendingRequests = + HashMap(); IOWebSocketChannel? _channel; // ignore: cancel_subscriptions StreamSubscription>? _connectivitySubscription; @@ -112,7 +116,7 @@ class ApiService { // Function is called after the user is authenticated at the server Future onAuthenticated() async { - await initFCMAfterAuthenticated(); + await FcmNotificationService.initFCMAfterAuthenticated(); _connectionStateController.add(true); if (AppState.isInBackgroundTask) { @@ -418,7 +422,9 @@ class ApiService { } if (res.error == ErrorCode.UserIdNotFound && contactId != null) { Log.warn('Contact deleted their account $contactId.'); - final contact = await twonlyDB.contactsDao.getContactByUserId(contactId).getSingleOrNull(); + final contact = await twonlyDB.contactsDao + .getContactByUserId(contactId) + .getSingleOrNull(); if (contact != null) { await twonlyDB.contactsDao.updateContact( contactId, @@ -483,7 +489,8 @@ class ApiService { return true; } if (result.isError) { - if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) { + if (result.error != ErrorCode.AuthTokenNotValid && + result.error != ErrorCode.ForegroundSessionConnected) { Log.error( 'got error while authenticating to the server: ${result.error}', ); @@ -521,7 +528,8 @@ class ApiService { return true; } if (result.isError) { - if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) { + if (result.error != ErrorCode.AuthTokenNotValid && + result.error != ErrorCode.ForegroundSessionConnected) { Log.error( 'got error while authenticating to the server: ${result.error}', ); @@ -553,7 +561,8 @@ class ApiService { return; } - final handshake = Handshake()..getAuthChallenge = Handshake_GetAuthChallenge(); + final handshake = Handshake() + ..getAuthChallenge = Handshake_GetAuthChallenge(); final req = createClientToServerFromHandshake(handshake); final result = await sendRequestSync(req, authenticated: false); @@ -618,7 +627,9 @@ class ApiService { final register = Handshake_Register() ..username = username - ..publicIdentityKey = (await signalStore.getIdentityKeyPair()).getPublicKey().serialize() + ..publicIdentityKey = (await signalStore.getIdentityKeyPair()) + .getPublicKey() + .serialize() ..registrationId = Int64(signalIdentity.registrationId) ..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize() ..signedPrekeySignature = signedPreKey.signature diff --git a/lib/src/services/api/server_messages.api.dart b/lib/src/services/api/server_messages.api.dart index b1833390..a9fc8f1a 100644 --- a/lib/src/services/api/server_messages.api.dart +++ b/lib/src/services/api/server_messages.api.dart @@ -5,6 +5,7 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; import 'package:mutex/mutex.dart'; + import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; @@ -281,7 +282,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw( Log.info('[$receiptId] Finished handleEncryptedMessage'); if (a == null && b == null) { - unawaited(updateLastServerMessageTimestamp()); + unawaited(FcmNotificationService.updateLastServerMessageTimestamp()); if (Platform.isAndroid) { // Message was handled without any error. Show push notification to the user for Android. await showPushNotificationFromServerMessages( diff --git a/lib/src/services/notifications/fcm.background.dart b/lib/src/services/notifications/fcm.background.dart new file mode 100644 index 00000000..ac058ff8 --- /dev/null +++ b/lib/src/services/notifications/fcm.background.dart @@ -0,0 +1,29 @@ +import 'dart:async'; +import 'dart:io' show Platform; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; +import 'package:twonly/src/services/notifications/fcm.notifications.dart'; +import 'package:twonly/src/services/notifications/setup.notifications.dart'; +import 'package:twonly/src/utils/log.dart'; + +@pragma('vm:entry-point') +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + SentryWidgetsFlutterBinding.ensureInitialized(); + await AppEnvironment.init(); + final isInitialized = await initBackgroundExecution(); + await setupPushNotification(); + Log.info('Handling a background message: ${message.messageId}'); + await FcmNotificationService.handleRemoteMessage(message); + + if (Platform.isAndroid) { + if (isInitialized) { + await handlePeriodicTask(lastExecutionInSecondsLimit: 10); + } + } else { + // make sure every thing run... + await Future.delayed(const Duration(milliseconds: 2000)); + } +} diff --git a/lib/src/services/notifications/fcm.notifications.dart b/lib/src/services/notifications/fcm.notifications.dart index fce869b5..b0fca4c8 100644 --- a/lib/src/services/notifications/fcm.notifications.dart +++ b/lib/src/services/notifications/fcm.notifications.dart @@ -1,5 +1,3 @@ -// ignore_for_file: unreachable_from_main - import 'dart:async'; import 'dart:io' show Platform; @@ -7,13 +5,11 @@ import 'package:firebase_app_installations/firebase_app_installations.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/secure_storage.keys.dart'; -import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; -import 'package:twonly/src/services/notifications/setup.notifications.dart'; +import 'package:twonly/src/services/notifications/fcm.background.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -21,240 +17,253 @@ import '../../../firebase_options.dart'; // see more here: https://firebase.google.com/docs/cloud-messaging/flutter/receive?hl=de -Future checkForTokenUpdates() async { - try { - if (!userService.isUserCreated) return; - if (Platform.isIOS) { - var apnsToken = await FirebaseMessaging.instance.getAPNSToken(); - for (var i = 0; i < 20; i++) { - if (apnsToken != null) break; - await Future.delayed(const Duration(seconds: 1)); - apnsToken = await FirebaseMessaging.instance.getAPNSToken(); - } - if (apnsToken == null) { - Log.error('Could not get APNS token even after 20s...'); +class FcmNotificationService { + static Future initStartup() async { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + + FirebaseMessaging.onMessage.listen(handleRemoteMessage); + } + + static Future initAfterUserLoaded() async { + unawaited(_checkForTokenUpdates()); + unawaited(_checkFcmHealthAndResetIfNeeded()); + } + + static Future initFCMAfterAuthenticated({bool force = false}) async { + final fcmToken = userService.currentUser.fcmToken; + if (userService.currentUser.updateFCMToken || force) { + if (fcmToken == null) { + Log.error('FCM token could not be updated as it is empty'); + await _checkForTokenUpdates(); return; } - } - - final fcmToken = await FirebaseMessaging.instance.getToken(); - if (fcmToken == null) { - Log.error('Could not get fcm token'); - return; - } - - Log.info('Loaded FCM token.'); - - if (userService.currentUser.fcmToken == null || - fcmToken != userService.currentUser.fcmToken) { - Log.info('Got new FCM token.'); - await UserService.update((u) { - u - ..updateFCMToken = true - ..fcmToken = fcmToken; - }); - } - - FirebaseMessaging.instance.onTokenRefresh - .listen((fcmToken) async { - await UserService.update((u) { - u - ..updateFCMToken = true - ..fcmToken = fcmToken; - }); - }) - .onError((err) { - Log.error('could not listen on token refresh'); + final res = await apiService.updateFCMToken( + fcmToken, + ); + if (res.isSuccess) { + Log.info('Uploaded new FCM token!'); + await UserService.update((u) { + u.updateFCMToken = false; }); - } catch (e) { - Log.error('could not load fcm token: $e'); + } else { + Log.error('Could not update FCM token!'); + } + } } -} -Future initFCMAfterAuthenticated({bool force = false}) async { - final fcmToken = userService.currentUser.fcmToken; - if (userService.currentUser.updateFCMToken || force) { - if (fcmToken == null) { - Log.error('FCM token could not be updated as it is empty'); - await checkForTokenUpdates(); + static Future resetFCMTokens() async { + await FirebaseInstallations.instance.delete(); + Log.info('Firebase Installation successfully deleted.'); + await FirebaseMessaging.instance.deleteToken(); + Log.info('Old FCM deleted.'); + await UserService.update((u) => u.fcmToken = null); + await _checkForTokenUpdates(); + await initFCMAfterAuthenticated(force: true); + } + + static Future _checkForTokenUpdates() async { + try { + if (!userService.isUserCreated) { + Log.info( + 'Checking for FCM token updates skipped: user is not yet created.', + ); + return; + } + if (Platform.isIOS) { + var apnsToken = await FirebaseMessaging.instance.getAPNSToken(); + for (var i = 0; i < 20; i++) { + if (apnsToken != null) break; + await Future.delayed(const Duration(seconds: 1)); + apnsToken = await FirebaseMessaging.instance.getAPNSToken(); + } + if (apnsToken == null) { + Log.error('Could not get APNS token even after 20s...'); + return; + } + } + + final fcmToken = await FirebaseMessaging.instance.getToken(); + if (fcmToken == null) { + Log.error('Could not get fcm token'); + return; + } + + Log.info('Loaded FCM token.'); + + if (userService.currentUser.fcmToken == null || + fcmToken != userService.currentUser.fcmToken) { + Log.info('Got new FCM token.'); + await UserService.update((u) { + u + ..updateFCMToken = true + ..fcmToken = fcmToken; + }); + if (apiService.isAuthenticated) { + final res = await apiService.updateFCMToken(fcmToken); + if (res.isSuccess) { + Log.info('Uploaded new FCM token!'); + await UserService.update((u) { + u.updateFCMToken = false; + }); + } else { + Log.error('Could not update FCM token!'); + } + } + } + + FirebaseMessaging.instance.onTokenRefresh + // ignore: avoid_types_on_closure_parameters + .listen((String fcmToken) async { + await UserService.update((u) { + u + ..updateFCMToken = true + ..fcmToken = fcmToken; + }); + if (apiService.isAuthenticated) { + final res = await apiService.updateFCMToken(fcmToken); + if (res.isSuccess) { + Log.info('Uploaded new FCM token!'); + await UserService.update((u) { + u.updateFCMToken = false; + }); + } else { + Log.error('Could not update FCM token!'); + } + } + }) + .onError((err) { + Log.error('could not listen on token refresh'); + }); + } catch (e) { + Log.error('could not load fcm token: $e'); + } + } + + static Future handleRemoteMessage(RemoteMessage message) async { + await _updateLastFcmMessageTimestamp(); + if (!Platform.isAndroid) { + Log.error('Got message in Dart while on iOS'); + } + if (message.notification != null && AppState.isAppInBackground) { + Log.error( + 'Got notification but app is in background, so the SDK already have shown the message.', + ); return; } - final res = await apiService.updateFCMToken( - fcmToken, - ); - if (res.isSuccess) { - Log.info('Uploaded new FCM token!'); - await UserService.update((u) { - u.updateFCMToken = false; - }); - } else { - Log.error('Could not update FCM token!'); + + if (message.notification != null || message.data['title'] != null) { + final title = + message.notification?.title ?? message.data['title'] as String? ?? ''; + final body = + message.notification?.body ?? message.data['body'] as String? ?? ''; + await customLocalPushNotification(title, body); } } -} -Future resetFCMTokens() async { - await FirebaseInstallations.instance.delete(); - Log.info('Firebase Installation successfully deleted.'); - await FirebaseMessaging.instance.deleteToken(); - Log.info('Old FCM deleted.'); - await UserService.update((u) => u.fcmToken = null); - await checkForTokenUpdates(); - await initFCMAfterAuthenticated(force: true); -} - -Future initFCMService() async { - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, - ); - - unawaited(checkForTokenUpdates()); - unawaited(checkFcmHealthAndResetIfNeeded()); - - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - - FirebaseMessaging.onMessage.listen(handleRemoteMessage); -} - -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - SentryWidgetsFlutterBinding.ensureInitialized(); - await AppEnvironment.init(); - final isInitialized = await initBackgroundExecution(); - await setupPushNotification(); - Log.info('Handling a background message: ${message.messageId}'); - await handleRemoteMessage(message); - - if (Platform.isAndroid) { - if (isInitialized) { - await handlePeriodicTask(lastExecutionInSecondsLimit: 10); + static Future _updateLastFcmMessageTimestamp() async { + const storage = FlutterSecureStorage(); + final nowMs = DateTime.now().millisecondsSinceEpoch.toString(); + try { + await storage.write( + key: SecureStorageKeys.lastFcmMessageTimestamp, + value: nowMs, + iOptions: const IOSOptions( + groupId: 'CN332ZUGRP.eu.twonly.shared', + accessibility: KeychainAccessibility.first_unlock, + ), + ); + Log.info('Updated last FCM message timestamp to $nowMs'); + } catch (e) { + Log.error('Could not write last FCM message timestamp: $e'); } - } else { - // make sure every thing run... - await Future.delayed(const Duration(milliseconds: 2000)); - } -} - -Future handleRemoteMessage(RemoteMessage message) async { - await updateLastFcmMessageTimestamp(); - if (!Platform.isAndroid) { - Log.error('Got message in Dart while on iOS'); - } - if (message.notification != null && AppState.isAppInBackground) { - Log.error( - 'Got notification but app is in background, so the SDK already have shown the message.', - ); - return; } - if (message.notification != null || message.data['title'] != null) { - final title = - message.notification?.title ?? message.data['title'] as String? ?? ''; - final body = - message.notification?.body ?? message.data['body'] as String? ?? ''; - await customLocalPushNotification(title, body); + static Future updateLastServerMessageTimestamp() async { + const storage = FlutterSecureStorage(); + final nowMs = DateTime.now().millisecondsSinceEpoch.toString(); + try { + await storage.write( + key: SecureStorageKeys.lastServerMessageTimestamp, + value: nowMs, + iOptions: const IOSOptions( + groupId: 'CN332ZUGRP.eu.twonly.shared', + accessibility: KeychainAccessibility.first_unlock, + ), + ); + Log.info('Updated last server message timestamp to $nowMs'); + } catch (e) { + Log.error('Could not write last server message timestamp: $e'); + } } - // On Android the push notification is now shown in the server_message.dart. This ensures - // that the messages was successfully decrypted before showing the push notification - // else if (message.data['push_data'] != null) { - // await handlePushData(message.data['push_data'] as String); - // } -} + static Future _checkFcmHealthAndResetIfNeeded() async { + if (!userService.isUserCreated) { + Log.info('FCM health check skipped: user is not yet created.'); + return; + } + const storage = FlutterSecureStorage(); + try { + final lastFcmStr = await storage.read( + key: SecureStorageKeys.lastFcmMessageTimestamp, + iOptions: const IOSOptions( + groupId: 'CN332ZUGRP.eu.twonly.shared', + accessibility: KeychainAccessibility.first_unlock, + ), + ); + final lastServerStr = await storage.read( + key: SecureStorageKeys.lastServerMessageTimestamp, + iOptions: const IOSOptions( + groupId: 'CN332ZUGRP.eu.twonly.shared', + accessibility: KeychainAccessibility.first_unlock, + ), + ); -Future updateLastFcmMessageTimestamp() async { - const storage = FlutterSecureStorage(); - final nowMs = DateTime.now().millisecondsSinceEpoch.toString(); - try { - await storage.write( - key: SecureStorageKeys.lastFcmMessageTimestamp, - value: nowMs, - iOptions: const IOSOptions( - groupId: 'CN332ZUGRP.eu.twonly.shared', - accessibility: KeychainAccessibility.first_unlock, - ), - ); - Log.info('Updated last FCM message timestamp to $nowMs'); - } catch (e) { - Log.error('Could not write last FCM message timestamp: $e'); - } -} + final now = DateTime.now(); + final threeDaysAgo = now.subtract(const Duration(days: 3)); -Future updateLastServerMessageTimestamp() async { - const storage = FlutterSecureStorage(); - final nowMs = DateTime.now().millisecondsSinceEpoch.toString(); - try { - await storage.write( - key: SecureStorageKeys.lastServerMessageTimestamp, - value: nowMs, - iOptions: const IOSOptions( - groupId: 'CN332ZUGRP.eu.twonly.shared', - accessibility: KeychainAccessibility.first_unlock, - ), - ); - Log.info('Updated last server message timestamp to $nowMs'); - } catch (e) { - Log.error('Could not write last server message timestamp: $e'); - } -} - -Future checkFcmHealthAndResetIfNeeded() async { - if (!userService.isUserCreated) return; - const storage = FlutterSecureStorage(); - try { - final lastFcmStr = await storage.read( - key: SecureStorageKeys.lastFcmMessageTimestamp, - iOptions: const IOSOptions( - groupId: 'CN332ZUGRP.eu.twonly.shared', - accessibility: KeychainAccessibility.first_unlock, - ), - ); - final lastServerStr = await storage.read( - key: SecureStorageKeys.lastServerMessageTimestamp, - iOptions: const IOSOptions( - groupId: 'CN332ZUGRP.eu.twonly.shared', - accessibility: KeychainAccessibility.first_unlock, - ), - ); - - final now = DateTime.now(); - final threeDaysAgo = now.subtract(const Duration(days: 3)); - - DateTime? lastFcmTime; - if (lastFcmStr != null) { - final ms = int.tryParse(lastFcmStr); - if (ms != null) { - lastFcmTime = DateTime.fromMillisecondsSinceEpoch(ms); + DateTime? lastFcmTime; + if (lastFcmStr != null) { + final ms = int.tryParse(lastFcmStr); + if (ms != null) { + lastFcmTime = DateTime.fromMillisecondsSinceEpoch(ms); + } } - } - if (lastFcmTime != null) { - Log.info('Last message received via FCM messaging system: $lastFcmTime'); - } else { - Log.info('No record of a message received via FCM messaging system.'); - } - - DateTime? lastServerTime; - if (lastServerStr != null) { - final ms = int.tryParse(lastServerStr); - if (ms != null) { - lastServerTime = DateTime.fromMillisecondsSinceEpoch(ms); + if (lastFcmTime != null) { + Log.info( + 'Last message received via FCM messaging system: $lastFcmTime', + ); + } else { + Log.info('No record of a message received via FCM messaging system.'); } - } - // Check conditions: - // 1. No messages received via FCM in the last 3 days (either null or older than 3 days) - final fcmInactive = lastFcmTime == null || lastFcmTime.isBefore(threeDaysAgo); - // 2. Server message received within the last 3 days - final serverActive = lastServerTime != null && lastServerTime.isAfter(threeDaysAgo); + DateTime? lastServerTime; + if (lastServerStr != null) { + final ms = int.tryParse(lastServerStr); + if (ms != null) { + lastServerTime = DateTime.fromMillisecondsSinceEpoch(ms); + } + } - if (fcmInactive && serverActive) { - Log.warn('FCM has been inactive for >3 days, but server messages have been active. Resetting FCM tokens...'); - await resetFCMTokens(); - } else { - Log.info('FCM check passed. No reset needed.'); + final fcmInactive = + lastFcmTime == null || lastFcmTime.isBefore(threeDaysAgo); + final serverActive = + lastServerTime != null && lastServerTime.isAfter(threeDaysAgo); + + if (fcmInactive && serverActive) { + Log.warn( + 'FCM has been inactive for >3 days, but server messages have been active. Resetting FCM tokens...', + ); + await resetFCMTokens(); + } else { + Log.info('FCM check passed. No reset needed.'); + } + } catch (e) { + Log.error('Error during FCM health check: $e'); } - } catch (e) { - Log.error('Error during FCM health check: $e'); } } diff --git a/lib/src/visual/views/onboarding/register.view.dart b/lib/src/visual/views/onboarding/register.view.dart index fde5a191..e3a4dd59 100644 --- a/lib/src/visual/views/onboarding/register.view.dart +++ b/lib/src/visual/views/onboarding/register.view.dart @@ -10,6 +10,7 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/model/json/userdata.model.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; +import 'package:twonly/src/services/notifications/fcm.notifications.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -163,6 +164,8 @@ class _RegisterViewState extends State { await UserService.save(userData); + unawaited(FcmNotificationService.initAfterUserLoaded()); + await apiService.authenticate(); widget.callbackOnSuccess(); } catch (e, stack) { diff --git a/lib/src/visual/views/settings/notification.view.dart b/lib/src/visual/views/settings/notification.view.dart index c8e3696e..cb81a962 100644 --- a/lib/src/visual/views/settings/notification.view.dart +++ b/lib/src/visual/views/settings/notification.view.dart @@ -45,7 +45,7 @@ class _NotificationViewState extends State { _isLoadingTroubleshooting = true; }); - await initFCMAfterAuthenticated(force: true); + await FcmNotificationService.initFCMAfterAuthenticated(force: true); await setupNotificationWithUsers(force: true); @@ -90,7 +90,7 @@ class _NotificationViewState extends State { setState(() { _isLoadingReset = true; }); - await resetFCMTokens(); + await FcmNotificationService.resetFCMTokens(); if (!mounted) return; await showAlertDialog( context,