diff --git a/lib/features/base/action/ui_action.dart b/lib/features/base/action/ui_action.dart index 0552a70e6..dfe9b368c 100644 --- a/lib/features/base/action/ui_action.dart +++ b/lib/features/base/action/ui_action.dart @@ -1,6 +1,17 @@ import 'package:equatable/equatable.dart'; +import 'package:fcm/model/type_name.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; abstract class Action with EquatableMixin {} -abstract class UIAction extends Action {} \ No newline at end of file +abstract class UIAction extends Action {} + +abstract class FcmAction extends Action {} + +abstract class FcmStateChangeAction extends FcmAction { + final TypeName typeName; + final jmap.State newState; + + FcmStateChangeAction(this.typeName, this.newState); +} \ No newline at end of file diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index b7b15536d..84356489b 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -1,9 +1,15 @@ import 'package:contact/contact/model/capability_contact.dart'; -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/app_toast.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/fps_manager.dart'; import 'package:dartz/dartz.dart'; -import 'package:firebase_core/firebase_core.dart'; +import 'package:fcm/model/firebase_capability.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:forward/forward/capability_forward.dart'; import 'package:get/get.dart'; @@ -20,24 +26,22 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/t import 'package:tmail_ui_user/features/manage_account/presentation/email_rules/bindings/email_rules_interactor_bindings.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/bindings/forwarding_interactors_bindings.dart'; import 'package:tmail_ui_user/features/push_notification/domain/exceptions/fcm_exception.dart'; -import 'package:tmail_ui_user/features/push_notification/domain/model/capability_push_notification.dart'; -import 'package:tmail_ui_user/features/push_notification/presentation/firebase_bindings.dart'; -import 'package:tmail_ui_user/features/push_notification/presentation/firebase_options.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/config/fcm_configuration.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_controller.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/notification/local_notification_manager.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; +import 'package:tmail_ui_user/main/utils/app_utils.dart'; abstract class BaseController extends GetxController with MessageDialogActionMixin, PopupContextMenuActionMixin, ViewAsDialogActionMixin { - final _appToast = Get.find(); - final _imagePaths = Get.find(); - final _responsiveUtils = Get.find(); - final viewState = Rx>(Right(UIState.idle)); FpsCallback? fpsCallback; @@ -66,6 +70,10 @@ abstract class BaseController extends GetxController } void onError(dynamic error) { + final _appToast = Get.find(); + final _imagePaths = Get.find(); + final _responsiveUtils = Get.find(); + String messageError = ''; if (error is MethodLevelErrors) { messageError = error.message ?? error.type.value; @@ -126,7 +134,7 @@ abstract class BaseController extends GetxController requireCapability(session!, accountId!, [CapabilityIdentifier.jmapMdn]); MdnInteractorBindings().dependencies(); } catch(e) { - logError('BaseController::injectVacationBindings(): exception: $e'); + logError('BaseController::injectMdnBindings(): exception: $e'); } } @@ -150,11 +158,13 @@ abstract class BaseController extends GetxController Future injectFCMBindings(Session? session, AccountId? accountId) async { try { - requireCapability(session!, accountId!, [capabilityPushNotification]); + requireCapability(session!, accountId!, [FirebaseCapability.fcmIdentifier]); if(AppConfig.fcmAvailable) { - await dotenv.load(fileName: AppConfig.appFCMConfigurationPath); - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - FCMBindings().dependencies(); + AppUtils.loadFcmConfigFile(); + FcmConfiguration.initialize(); + await LocalNotificationManager.instance.setUp(); + FcmInteractorBindings().dependencies(); + FcmController.instance.initialize(accountId: accountId); } else { throw NotSupportFCMException(); } diff --git a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart index 0f0b63721..c27de0c5d 100644 --- a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart +++ b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart @@ -5,6 +5,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; class DashBoardAction extends UIAction { static final idle = DashBoardAction(); @@ -130,4 +131,14 @@ class SearchEmailByQueryAction extends DashBoardAction { @override List get props => [navigationRouter]; +} + +class RefreshChangedEmailAction extends DashBoardAction { + + final jmap.State? newState; + + RefreshChangedEmailAction(this.newState); + + @override + List get props => [newState]; } \ No newline at end of file diff --git a/lib/features/push_notification/domain/model/capability_push_notification.dart b/lib/features/push_notification/domain/model/capability_push_notification.dart deleted file mode 100644 index d523110cc..000000000 --- a/lib/features/push_notification/domain/model/capability_push_notification.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; - -final capabilityPushNotification = CapabilityIdentifier(Uri.parse('com:linagora:params:jmap:firebase:push')); \ No newline at end of file diff --git a/lib/features/push_notification/presentation/action/fcm_action.dart b/lib/features/push_notification/presentation/action/fcm_action.dart new file mode 100644 index 000000000..5a61a9bfb --- /dev/null +++ b/lib/features/push_notification/presentation/action/fcm_action.dart @@ -0,0 +1,47 @@ + +import 'package:fcm/model/type_name.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:tmail_ui_user/features/base/action/ui_action.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; + +class SynchronizeEmailOnForegroundAction extends FcmStateChangeAction { + + final AccountId accountId; + + SynchronizeEmailOnForegroundAction( + TypeName typeName, + jmap.State newState, + this.accountId + ) : super(typeName, newState); + + @override + List get props => [typeName, newState, accountId]; +} + +class PushNotificationAction extends FcmStateChangeAction { + + final AccountId accountId; + + PushNotificationAction( + TypeName typeName, + jmap.State newState, + this.accountId + ) : super(typeName, newState); + + @override + List get props => [typeName, newState, accountId]; +} + +class StoreEmailStateChangeToRefreshAction extends FcmStateChangeAction { + + final AccountId accountId; + + StoreEmailStateChangeToRefreshAction( + TypeName typeName, + jmap.State newState, + this.accountId + ) : super(typeName, newState); + + @override + List get props => [typeName, newState, accountId]; +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/config/fcm_configuration.dart b/lib/features/push_notification/presentation/config/fcm_configuration.dart new file mode 100644 index 000000000..9d256e53b --- /dev/null +++ b/lib/features/push_notification/presentation/config/fcm_configuration.dart @@ -0,0 +1,19 @@ + +import 'package:firebase_core/firebase_core.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/config/firebase_options.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_receiver.dart'; + +class FcmConfiguration { + + static void initialize() async { + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + _initMessageListener(); + } + + static void _initMessageListener() { + FcmReceiver.instance.onForegroundMessage(); + FcmReceiver.instance.onMessageOpenedApp(); + FcmReceiver.instance.onBackgroundMessage(); + FcmReceiver.instance.getFcmToken(); + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/controller/fcm_controller.dart b/lib/features/push_notification/presentation/controller/fcm_controller.dart new file mode 100644 index 000000000..f67fe3a3c --- /dev/null +++ b/lib/features/push_notification/presentation/controller/fcm_controller.dart @@ -0,0 +1,242 @@ + +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:core/data/network/config/dynamic_url_interceptors.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart'; +import 'package:fcm/model/type_name.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; +import 'package:model/oidc/token_oidc.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:tmail_ui_user/features/base/action/ui_action.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; +import 'package:tmail_ui_user/features/home/presentation/home_bindings.dart'; +import 'package:tmail_ui_user/features/login/data/network/config/authorization_interceptors.dart'; +import 'package:tmail_ui_user/features/login/domain/state/get_credential_state.dart'; +import 'package:tmail_ui_user/features/login/domain/state/get_stored_token_oidc_state.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/action/fcm_action.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/extensions/state_change_extension.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/listener/email_change_listener.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/utils/fcm_utils.dart'; +import 'package:tmail_ui_user/features/session/domain/state/get_session_state.dart'; +import 'package:tmail_ui_user/features/session/domain/usecases/get_session_interactor.dart'; +import 'package:tmail_ui_user/main/bindings/main_bindings.dart'; + +class FcmController extends BaseController { + + AccountId? _currentAccountId; + RemoteMessage? _remoteMessageBackground; + + GetAuthenticatedAccountInteractor? _getAuthenticatedAccountInteractor; + DynamicUrlInterceptors? _dynamicUrlInterceptors; + AuthorizationInterceptors? _authorizationInterceptors; + GetSessionInteractor? _getSessionInteractor; + + FcmController._internal() { + _listenFcmMessageStream(); + } + + static final FcmController _instance = FcmController._internal(); + + static FcmController get instance => _instance; + + void initialize({AccountId? accountId}) { + _currentAccountId = accountId; + } + + void _listenFcmMessageStream() { + log('FcmController::_listenFcmMessageStream():'); + FcmService.instance.foregroundMessageStream + .throttleTime(const Duration(milliseconds: FcmService.durationMessageComing)) + .listen(_handleForegroundMessageAction); + + FcmService.instance.backgroundMessageStream + .throttleTime(const Duration(milliseconds: FcmService.durationMessageComing)) + .listen(_handleBackgroundMessageAction); + } + + void _handleForegroundMessageAction(RemoteMessage newRemoteMessage) { + log('FcmController::_handleForegroundMessageAction():remoteMessage: ${newRemoteMessage.data}'); + if (_currentAccountId != null) { + final stateChange = _convertRemoteMessageToStateChange(newRemoteMessage); + final mapTypeState = stateChange.getMapTypeState(_currentAccountId!); + _mappingTypeStateToAction(mapTypeState, _currentAccountId!); + } + } + + void _handleBackgroundMessageAction(RemoteMessage newRemoteMessage) async { + log('FcmController::_handleBackgroundMessageAction():remoteMessage: ${newRemoteMessage.data}'); + _remoteMessageBackground = newRemoteMessage; + await _initialAppConfig(); + _getAuthenticatedAccount(); + } + + StateChange? _convertRemoteMessageToStateChange(RemoteMessage newRemoteMessage) { + return FcmUtils.instance.convertFirebaseDataMessageToStateChange(newRemoteMessage.data); + } + + void _mappingTypeStateToAction( + Map mapTypeState, + AccountId accountId, { + bool isForeground = true, + }) { + log('FcmController::_mappingTypeStateToAction():mapTypeState: $mapTypeState'); + final listTypeName = mapTypeState.keys + .map((value) => TypeName(value)) + .toList(); + + final listEmailActions = listTypeName + .map((typeName) => toFcmAction(typeName, accountId, mapTypeState, isForeground)) + .whereNotNull() + .toList(); + + log('FcmController::_mappingTypeStateToAction():listEmailActions: $listEmailActions'); + + if (listEmailActions.isNotEmpty) { + EmailChangeListener.instance.dispatchActions(listEmailActions); + } + } + + FcmAction? toFcmAction( + TypeName typeName, + AccountId accountId, + Map mapTypeState, + isForeground + ) { + final newState = jmap.State(mapTypeState[typeName.value]); + if (typeName == TypeName.emailType) { + if (isForeground) { + return SynchronizeEmailOnForegroundAction(typeName, newState, accountId); + } else { + return StoreEmailStateChangeToRefreshAction(typeName, newState, accountId); + } + } else if (typeName == TypeName.emailDelivery) { + if (!isForeground) { + return PushNotificationAction(typeName, newState, accountId); + } + } + return null; + } + + Future _initialAppConfig() async { + await Future.wait([ + MainBindings().dependencies(), + HiveCacheConfig().setUp() + ]); + + await Future.sync(() { + HomeBindings().dependencies(); + MailboxDashBoardBindings().dependencies(); + }); + + _getInteractorBindings(); + } + + void _getInteractorBindings() { + try { + _getAuthenticatedAccountInteractor = Get.find(); + _dynamicUrlInterceptors = Get.find(); + _authorizationInterceptors = Get.find(); + _getSessionInteractor = Get.find(); + } catch (e) { + logError('FcmController::_getBindings(): ${e.toString()}'); + } + } + + void _getAuthenticatedAccount() async { + if (_getAuthenticatedAccountInteractor != null) { + consumeState(_getAuthenticatedAccountInteractor!.execute()); + } else { + _clearRemoteMessageBackground(); + logError('FcmController::_getAuthenticatedAccount():_getAuthenticatedAccountInteractor is null'); + } + } + + void _handleFailureViewState(Failure failure) { + log('FcmController::_handleFailureViewState(): $failure'); + _clearRemoteMessageBackground(); + } + + void _handleSuccessViewState(Success success) { + if (success is GetStoredTokenOidcSuccess) { + _getSessionWithTokenOidc(success); + } else if (success is GetCredentialViewState) { + _getSessionWithBasicAuth(success); + } else if (success is GetSessionSuccess) { + _handleGetSessionSuccess(success); + } + } + + void _getSessionWithTokenOidc(GetStoredTokenOidcSuccess storedTokenOidcSuccess) { + _dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.baseUrl.toString()); + _authorizationInterceptors?.setTokenAndAuthorityOidc( + newToken: storedTokenOidcSuccess.tokenOidc.toToken(), + newConfig: storedTokenOidcSuccess.oidcConfiguration + ); + _getSession(); + } + + void _getSessionWithBasicAuth(GetCredentialViewState credentialViewState) { + _dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.baseUrl.origin); + _authorizationInterceptors?.setBasicAuthorization( + credentialViewState.userName.userName, + credentialViewState.password.value, + ); + _getSession(); + } + + void _getSession() async { + if (_getSessionInteractor != null) { + consumeState(_getSessionInteractor!.execute().asStream()); + } else { + _clearRemoteMessageBackground(); + logError('FcmController::_getSession(): _getSessionInteractor is null'); + } + } + + void _handleGetSessionSuccess(GetSessionSuccess success) { + final sessionCurrent = success.session; + final apiUrl = sessionCurrent.apiUrl.toString(); + _currentAccountId = sessionCurrent.accounts.keys.first; + + _dynamicUrlInterceptors?.changeBaseUrl(apiUrl); + + if (_remoteMessageBackground != null && _currentAccountId != null) { + final stateChange = _convertRemoteMessageToStateChange(_remoteMessageBackground!); + final mapTypeState = stateChange.getMapTypeState(_currentAccountId!); + _mappingTypeStateToAction(mapTypeState, _currentAccountId!, isForeground: false); + } + _clearRemoteMessageBackground(); + } + + void _clearRemoteMessageBackground() { + _remoteMessageBackground = null; + } + + @override + void dispose() { + _clearRemoteMessageBackground(); + FcmService.instance.dispose(); + super.dispose(); + } + + @override + void onData(Either newState) { + super.onData(newState); + newState.fold(_handleFailureViewState, _handleSuccessViewState); + } + + @override + void onDone() {} +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/extensions/state_change_extension.dart b/lib/features/push_notification/presentation/extensions/state_change_extension.dart new file mode 100644 index 000000000..a36330fff --- /dev/null +++ b/lib/features/push_notification/presentation/extensions/state_change_extension.dart @@ -0,0 +1,10 @@ + +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; + +extension StateChangeExtension on StateChange? { + + Map getMapTypeState(AccountId accountId) { + return this?.changed[accountId]?.typeState ?? {}; + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/listener/change_listener.dart b/lib/features/push_notification/presentation/listener/change_listener.dart new file mode 100644 index 000000000..10213ffad --- /dev/null +++ b/lib/features/push_notification/presentation/listener/change_listener.dart @@ -0,0 +1,6 @@ + +import 'package:tmail_ui_user/features/base/action/ui_action.dart'; + +abstract class ChangeListener { + void dispatchActions(List actions); +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/listener/email_change_listener.dart b/lib/features/push_notification/presentation/listener/email_change_listener.dart new file mode 100644 index 000000000..d56363cb3 --- /dev/null +++ b/lib/features/push_notification/presentation/listener/email_change_listener.dart @@ -0,0 +1,56 @@ + +import 'package:core/utils/app_logger.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:tmail_ui_user/features/base/action/ui_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/action/fcm_action.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/listener/change_listener.dart'; + +class EmailChangeListener extends ChangeListener { + + MailboxDashBoardController? _dashBoardController; + + EmailChangeListener._internal() { + try { + _dashBoardController = Get.find(); + } catch (e) { + logError('EmailChangeListener::_internal(): IS NOT REGISTERED: ${e.toString()}'); + } + } + + static final EmailChangeListener _instance = EmailChangeListener._internal(); + + static EmailChangeListener get instance => _instance; + + @override + void dispatchActions(List actions) { + log('EmailChangeListener::dispatchActions():actions: $actions'); + for (var action in actions) { + if (action is SynchronizeEmailOnForegroundAction) { + _synchronizeEmailOnForegroundAction(action.newState); + } else if (action is PushNotificationAction) { + _pushNotificationAction(action.newState, action.accountId); + } else if (action is StoreEmailStateChangeToRefreshAction) { + _handleStoreEmailStateChangeToRefreshAction(action.newState, action.accountId); + } + } + } + + void _synchronizeEmailOnForegroundAction(jmap.State newState) { + log('EmailChangeListener::_synchronizeEmailAction():newState: $newState'); + if (_dashBoardController != null) { + _dashBoardController!.dispatchAction(RefreshChangedEmailAction(newState)); + } + } + + void _pushNotificationAction(jmap.State newState, AccountId accountId) { + log('EmailChangeListener::_pushNotificationAction():newState: $newState'); + } + + void _handleStoreEmailStateChangeToRefreshAction(jmap.State newState, AccountId accountId) { + log('EmailChangeListener::_handleStoreEmailStateChangeToRefreshAction():newState: $newState'); + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/services/fcm_receiver.dart b/lib/features/push_notification/presentation/services/fcm_receiver.dart new file mode 100644 index 000000000..bddbb8a42 --- /dev/null +++ b/lib/features/push_notification/presentation/services/fcm_receiver.dart @@ -0,0 +1,43 @@ + +import 'package:core/utils/app_logger.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart'; + +int semaphoreBackground = 0; + +@pragma('vm:entry-point') +Future handleFirebaseBackgroundMessage(RemoteMessage message) async { + if (semaphoreBackground != 0) { + return; + } + semaphoreBackground = 1; + Future.delayed(const Duration(milliseconds: FcmService.durationMessageComing)).then((_) => semaphoreBackground = 0); + + log('FcmReceiver::handleFirebaseBackgroundMessage(): ${message.data}'); + FcmService.instance.handleFirebaseBackgroundMessage(message); +} + +class FcmReceiver { + FcmReceiver._internal(); + + static final FcmReceiver _instance = FcmReceiver._internal(); + + static FcmReceiver get instance => _instance; + + void onForegroundMessage() { + FirebaseMessaging.onMessage.listen(FcmService.instance.handleFirebaseForegroundMessage); + } + + void onMessageOpenedApp() { + FirebaseMessaging.onMessageOpenedApp.listen(FcmService.instance.handleFirebaseMessageOpenedApp); + } + + void onBackgroundMessage() { + FirebaseMessaging.onBackgroundMessage(handleFirebaseBackgroundMessage); + } + + void getFcmToken() async { + final token = await FirebaseMessaging.instance.getToken(); + log('FcmReceiver::onFcmToken():token: $token'); + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/services/fcm_service.dart b/lib/features/push_notification/presentation/services/fcm_service.dart new file mode 100644 index 000000000..db78a56b0 --- /dev/null +++ b/lib/features/push_notification/presentation/services/fcm_service.dart @@ -0,0 +1,55 @@ + +import 'dart:async'; + +import 'package:core/utils/app_logger.dart'; +import 'package:fcm/model/firebase_token.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_controller.dart'; + +class FcmService { + + static const int durationMessageComing = 2000; + + final StreamController foregroundMessageStreamController = StreamController.broadcast(); + Stream get foregroundMessageStream => foregroundMessageStreamController.stream; + + final StreamController backgroundMessageStreamController = StreamController.broadcast(); + Stream get backgroundMessageStream => backgroundMessageStreamController.stream; + + FirebaseToken? currentToken; + int semaphore = 0; + + FcmService._internal(); + + static final FcmService _instance = FcmService._internal(); + + static FcmService get instance => _instance; + + void handleFirebaseForegroundMessage(RemoteMessage newRemoteMessage) { + if (semaphore != 0) { + return; + } + semaphore = 1; + Future.delayed(const Duration(milliseconds: durationMessageComing)).then((_) => semaphore = 0); + + foregroundMessageStreamController.add(newRemoteMessage); + } + + void handleFirebaseBackgroundMessage(RemoteMessage newRemoteMessage) { + FcmController.instance.initialize(); + backgroundMessageStreamController.add(newRemoteMessage); + } + + void handleFirebaseMessageOpenedApp(RemoteMessage newRemoteMessage) { + log('FcmService::handleFirebaseMessageOpenedApp():newRemoteMessage: ${newRemoteMessage.data}'); + } + + void _closeStream() { + foregroundMessageStreamController.close(); + backgroundMessageStreamController.close(); + } + + void dispose() { + _closeStream(); + } +} \ No newline at end of file diff --git a/lib/main/bindings/local/local_bindings.dart b/lib/main/bindings/local/local_bindings.dart index f11d0440b..8d0c07e85 100644 --- a/lib/main/bindings/local/local_bindings.dart +++ b/lib/main/bindings/local/local_bindings.dart @@ -23,6 +23,7 @@ import 'package:tmail_ui_user/features/login/data/local/oidc_configuration_cache import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; +import 'package:tmail_ui_user/features/push_notification/data/local/fcm_cache_manager.dart'; import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; @@ -57,6 +58,7 @@ class LocalBindings extends Bindings { Get.put(RecentLoginUsernameCacheClient()); Get.put(RecentLoginUsernameCacheManager(Get.find())); Get.put(FcmTokenCacheClient()); + Get.put(FCMCacheManager((Get.find()))); Get.put(CachingManager( Get.find(), Get.find(),