diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 3af670ccd..fb6d72f6e 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -242,7 +242,7 @@ abstract class BaseController extends GetxController await AppUtils.loadFcmConfigFileToEnv(currentMapEnvData: mapEnvData); FcmConfiguration.initialize(); FcmInteractorBindings().dependencies(); - FcmMessageController.instance.initializeFromAccountId(accountId); + FcmMessageController.instance.initializeFromAccountId(accountId, session); if (!BuildUtils.isWeb) { LocalNotificationManager.instance.setUp(); } diff --git a/lib/features/push_notification/data/repository/fcm_repository_impl.dart b/lib/features/push_notification/data/repository/fcm_repository_impl.dart index c5d97e2d1..df0848287 100644 --- a/lib/features/push_notification/data/repository/fcm_repository_impl.dart +++ b/lib/features/push_notification/data/repository/fcm_repository_impl.dart @@ -5,6 +5,9 @@ import 'package:fcm/model/type_name.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:model/extensions/list_email_extension.dart'; import 'package:model/extensions/mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; @@ -133,4 +136,51 @@ class FCMRepositoryImpl extends FCMRepository { return mailboxesNotPutNotifications; } } + + @override + Future> getEmailChangesToRemoveNotification( + Session session, + AccountId accountId, + jmap.State currentState, + { + Properties? propertiesCreated, + Properties? propertiesUpdated + } + ) async { + EmailChangeResponse? emailChangeResponse; + bool hasMoreChanges = true; + jmap.State? sinceState = currentState; + + while (hasMoreChanges && sinceState != null) { + final changesResponse = await _threadDataSource.getChanges( + session, + accountId, + sinceState, + propertiesCreated: propertiesCreated, + propertiesUpdated: propertiesUpdated + ); + + hasMoreChanges = changesResponse.hasMoreChanges; + sinceState = changesResponse.newStateChanges; + + if (emailChangeResponse != null) { + emailChangeResponse.union(changesResponse); + } else { + emailChangeResponse = changesResponse; + } + } + + if (emailChangeResponse != null) { + final listEmailIdMarkAsRead = emailChangeResponse.updated + ?.where((email) => email.keywords?.containsKey(KeyWordIdentifier.emailSeen) == true) + .toList() + .listEmailIds ?? []; + final listEmailIdDestroyed = emailChangeResponse.destroyed ?? []; + log('FCMRepositoryImpl::getEmailChangesToRemoveNotification():listEmailIdMarkAsRead: $listEmailIdMarkAsRead | listEmailIdDestroyed: $listEmailIdDestroyed'); + final allListEmailIdsNeedRemove = listEmailIdMarkAsRead + listEmailIdDestroyed; + return allListEmailIdsNeedRemove; + } else { + return []; + } + } } \ No newline at end of file diff --git a/lib/features/push_notification/domain/state/get_email_changes_state.dart b/lib/features/push_notification/domain/state/get_email_changes_to_push_notification_state.dart similarity index 87% rename from lib/features/push_notification/domain/state/get_email_changes_state.dart rename to lib/features/push_notification/domain/state/get_email_changes_to_push_notification_state.dart index bb18aba64..da8b605f6 100644 --- a/lib/features/push_notification/domain/state/get_email_changes_state.dart +++ b/lib/features/push_notification/domain/state/get_email_changes_to_push_notification_state.dart @@ -16,9 +16,8 @@ class GetEmailChangesToPushNotificationSuccess extends UIState { } class GetEmailChangesToPushNotificationFailure extends FeatureFailure { - final dynamic exception; - GetEmailChangesToPushNotificationFailure(this.exception); + GetEmailChangesToPushNotificationFailure(exception) : super(exception: exception); @override List get props => [exception]; diff --git a/lib/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart b/lib/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart index 6df61384f..9bb84ec0e 100644 --- a/lib/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart +++ b/lib/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart @@ -7,7 +7,7 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:model/extensions/email_extension.dart'; import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_repository.dart'; -import 'package:tmail_ui_user/features/push_notification/domain/state/get_email_changes_state.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/state/get_email_changes_to_push_notification_state.dart'; class GetEmailChangesToPushNotificationInteractor { final FCMRepository _fcmRepository; diff --git a/lib/features/push_notification/presentation/action/fcm_action.dart b/lib/features/push_notification/presentation/action/fcm_action.dart index 34e757730..0dce6b73b 100644 --- a/lib/features/push_notification/presentation/action/fcm_action.dart +++ b/lib/features/push_notification/presentation/action/fcm_action.dart @@ -8,15 +8,17 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; class SynchronizeEmailOnForegroundAction extends FcmStateChangeAction { final AccountId accountId; + final Session? session; SynchronizeEmailOnForegroundAction( TypeName typeName, jmap.State newState, - this.accountId + this.accountId, + this.session ) : super(typeName, newState); @override - List get props => [typeName, newState, accountId]; + List get props => [typeName, newState, accountId, session]; } class PushNotificationAction extends FcmStateChangeAction { @@ -38,15 +40,17 @@ class PushNotificationAction extends FcmStateChangeAction { class StoreEmailStateToRefreshAction extends FcmStateChangeAction { final AccountId accountId; + final Session? session; StoreEmailStateToRefreshAction( TypeName typeName, jmap.State newState, - this.accountId + this.accountId, + this.session ) : super(typeName, newState); @override - List get props => [typeName, newState, accountId]; + List get props => [typeName, newState, accountId, session]; } class SynchronizeMailboxOnForegroundAction extends FcmStateChangeAction { diff --git a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart index 13d1961f4..97ebe9624 100644 --- a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart +++ b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart @@ -1,9 +1,21 @@ import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:core/data/network/dio_client.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; +import 'package:tmail_ui_user/features/caching/state_cache_client.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; +import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; +import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/state_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_isolate_worker.dart'; @@ -17,6 +29,7 @@ import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_r import 'package:tmail_ui_user/features/push_notification/domain/usecases/delete_email_state_to_refresh_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/destroy_subscription_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_changes_to_remove_notification_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_state_to_refresh_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_fcm_subscription_local_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart'; @@ -42,6 +55,9 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override @@ -66,6 +82,16 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => MailboxCacheDataSourceImpl( Get.find(), Get.find())); + Get.lazyPut(() => EmailDataSourceImpl( + Get.find(), + Get.find())); + Get.lazyPut(() => HtmlDataSourceImpl( + Get.find(), + Get.find(), + Get.find())); + Get.lazyPut(() => StateDataSourceImpl( + Get.find(), + Get.find())); } @override @@ -73,6 +99,9 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => GetStoredEmailDeliveryStateInteractor(Get.find())); Get.lazyPut(() => StoreEmailDeliveryStateInteractor(Get.find())); Get.lazyPut(() => GetEmailChangesToPushNotificationInteractor(Get.find())); + Get.lazyPut(() => GetEmailChangesToRemoveNotificationInteractor( + Get.find(), + Get.find())); Get.lazyPut(() => StoreEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => GetEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => DeleteEmailStateToRefreshInteractor(Get.find())); @@ -89,6 +118,7 @@ class FcmInteractorBindings extends InteractorsBindings { @override void bindingsRepository() { Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override @@ -104,5 +134,9 @@ class FcmInteractorBindings extends InteractorsBindings { DataSourceType.network: Get.find(), }, )); + Get.lazyPut(() => EmailRepositoryImpl( + Get.find(), + Get.find(), + Get.find())); } } diff --git a/lib/features/push_notification/presentation/controller/fcm_message_controller.dart b/lib/features/push_notification/presentation/controller/fcm_message_controller.dart index 32f70bb1c..69f6a6e38 100644 --- a/lib/features/push_notification/presentation/controller/fcm_message_controller.dart +++ b/lib/features/push_notification/presentation/controller/fcm_message_controller.dart @@ -57,8 +57,9 @@ class FcmMessageController extends FcmBaseController { static FcmMessageController get instance => _instance; - void initializeFromAccountId(AccountId accountId) { + void initializeFromAccountId(AccountId accountId, Session session) { _currentAccountId = accountId; + _currentSession = session; FcmTokenController.instance.initialize(); } @@ -98,7 +99,7 @@ class FcmMessageController extends FcmBaseController { if (_currentAccountId != null) { final stateChange = _convertRemoteMessageToStateChange(newRemoteMessage); final mapTypeState = stateChange.getMapTypeState(_currentAccountId!); - _mappingTypeStateToAction(mapTypeState, _currentAccountId!); + _mappingTypeStateToAction(mapTypeState, _currentAccountId!, session: _currentSession); } } @@ -161,9 +162,9 @@ class FcmMessageController extends FcmBaseController { final newState = jmap.State(mapTypeState[typeName.value]); if (typeName == TypeName.emailType) { if (isForeground) { - return SynchronizeEmailOnForegroundAction(typeName, newState, accountId); + return SynchronizeEmailOnForegroundAction(typeName, newState, accountId, session); } else { - return StoreEmailStateToRefreshAction(typeName, newState, accountId); + return StoreEmailStateToRefreshAction(typeName, newState, accountId, session); } } else if (typeName == TypeName.emailDelivery) { if (!isForeground) { diff --git a/lib/features/push_notification/presentation/listener/email_change_listener.dart b/lib/features/push_notification/presentation/listener/email_change_listener.dart index 66c33b014..c17d984ae 100644 --- a/lib/features/push_notification/presentation/listener/email_change_listener.dart +++ b/lib/features/push_notification/presentation/listener/email_change_listener.dart @@ -6,8 +6,11 @@ import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/build_utils.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:model/email/email_property.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/list_presentation_email_extension.dart'; import 'package:model/extensions/list_presentation_mailbox_extension.dart'; @@ -18,10 +21,12 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_stored_email_st import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/push_notification/domain/exceptions/fcm_exception.dart'; -import 'package:tmail_ui_user/features/push_notification/domain/state/get_email_changes_state.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/state/get_email_changes_to_push_notification_state.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/state/get_email_changes_to_remove_notification_state.dart'; import 'package:tmail_ui_user/features/push_notification/domain/state/get_mailboxes_not_put_notifications_state.dart'; import 'package:tmail_ui_user/features/push_notification/domain/state/get_stored_email_delivery_state.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_changes_to_remove_notification_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_mailboxes_not_put_notifications_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_stored_email_delivery_state_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_email_delivery_state_interactor.dart'; @@ -44,8 +49,9 @@ class EmailChangeListener extends ChangeListener { GetStoredEmailStateInteractor? _getStoredEmailStateInteractor; StoreEmailStateToRefreshInteractor? _storeEmailStateToRefreshInteractor; GetMailboxesNotPutNotificationsInteractor? _getMailboxesNotPutNotificationsInteractor; + GetEmailChangesToRemoveNotificationInteractor? _getEmailChangesToRemoveNotificationInteractor; - jmap.State? _newState; + jmap.State? _newStateEmailDelivery; AccountId? _accountId; Session? _session; List _emailsAvailablePushNotification = []; @@ -59,6 +65,7 @@ class EmailChangeListener extends ChangeListener { _getEmailChangesToPushNotificationInteractor = getBinding(); _storeEmailStateToRefreshInteractor = getBinding(); _getMailboxesNotPutNotificationsInteractor = getBinding(); + _getEmailChangesToRemoveNotificationInteractor = getBinding(); } catch (e) { logError('EmailChangeListener::_internal(): IS NOT REGISTERED: ${e.toString()}'); } @@ -73,10 +80,16 @@ class EmailChangeListener extends ChangeListener { log('EmailChangeListener::dispatchActions():actions: $actions'); for (var action in actions) { if (action is SynchronizeEmailOnForegroundAction) { + if (FcmUtils.instance.isMobileAndroid) { + _handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session); + } _synchronizeEmailOnForegroundAction(action.newState); } else if (action is PushNotificationAction) { _pushNotificationAction(action.newState, action.accountId, action.session); } else if (action is StoreEmailStateToRefreshAction) { + if (FcmUtils.instance.isMobileAndroid) { + _handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session); + } _handleStoreEmailStateToRefreshAction(action.newState); } } @@ -90,27 +103,25 @@ class EmailChangeListener extends ChangeListener { } void _pushNotificationAction(jmap.State newState, AccountId accountId, Session? session) { - _newState = newState; + _newStateEmailDelivery = newState; _accountId = accountId; _session = session; log('EmailChangeListener::_pushNotificationAction():newState: $newState'); if (BuildUtils.isWeb) { - _storeEmailDeliveryStateAction(_newState!); + _storeEmailDeliveryStateAction(_newStateEmailDelivery!); } else { if (Platform.isAndroid) { _getStoredEmailDeliveryState(); } else if (Platform.isIOS) { - _storeEmailDeliveryStateAction(_newState!); - _showLocalNotificationForIOS(_newState!, _accountId!); + _storeEmailDeliveryStateAction(_newStateEmailDelivery!); + _showLocalNotificationForIOS(_newStateEmailDelivery!, _accountId!); } else { logError('EmailChangeListener::_pushNotificationAction(): NOT SUPPORTED PLATFORM'); } } } - - void _getStoredEmailDeliveryState() { if (_getStoredEmailDeliveryStateInteractor != null) { consumeState(_getStoredEmailDeliveryStateInteractor!.execute()); @@ -186,14 +197,14 @@ class EmailChangeListener extends ChangeListener { void handleSuccessViewState(Success success) { log('EmailChangeListener::_handleSuccessViewState(): $success'); if (success is GetStoredEmailDeliveryStateSuccess) { - if (_newState != success.state) { + if (_newStateEmailDelivery != success.state) { _getEmailChangesAction(success.state); } } else if (success is GetStoredEmailStateSuccess) { _getEmailChangesAction(success.state); } else if (success is GetEmailChangesToPushNotificationSuccess) { - if (_newState != null) { - _storeEmailDeliveryStateAction(_newState!); + if (_newStateEmailDelivery != null) { + _storeEmailDeliveryStateAction(_newStateEmailDelivery!); if (FcmUtils.instance.isMobileAndroid) { _handleListEmailToPushNotification(success.emailList); @@ -203,6 +214,8 @@ class EmailChangeListener extends ChangeListener { final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification( mailboxIdsNotPutNotifications: success.mailboxes.mailboxIds); _handleLocalPushNotification(listEmails); + } else if (success is GetEmailChangesToRemoveNotificationSuccess) { + _handleRemoveLocalNotification(success.emailIds); } } @@ -239,4 +252,22 @@ class EmailChangeListener extends ChangeListener { logError('EmailChangeListener::_handleStoreEmailStateToRefreshAction():_storeEmailStateToRefreshInteractor is null'); } } + + void _handleRemoveNotificationWhenEmailMarkAsRead(jmap.State newState, AccountId accountId, Session? session) { + if (_getEmailChangesToRemoveNotificationInteractor != null && session != null) { + consumeState(_getEmailChangesToRemoveNotificationInteractor!.execute( + session, + accountId, + newState, + propertiesCreated: Properties({EmailProperty.id, EmailProperty.keywords}), + propertiesUpdated: Properties({EmailProperty.keywords}), + )); + } + } + + void _handleRemoveLocalNotification(List emailIds) async { + log('EmailChangeListener::_handleRemoveLocalNotification():emailIds: $emailIds'); + await Future.wait(emailIds.map((emailId) => LocalNotificationManager.instance.removeNotification(emailId.id.value))); + LocalNotificationManager.instance.removeGroupPushNotification(); + } } \ No newline at end of file diff --git a/lib/features/push_notification/presentation/notification/local_notification_config.dart b/lib/features/push_notification/presentation/notification/local_notification_config.dart index 975979bf1..65612ca88 100644 --- a/lib/features/push_notification/presentation/notification/local_notification_config.dart +++ b/lib/features/push_notification/presentation/notification/local_notification_config.dart @@ -9,6 +9,7 @@ class LocalNotificationConfig { static const _channelDescription = 'Team Mail notifications'; static const notificationTitle = 'Team Mail'; static const notificationMessage = 'You have new messages'; + static const int groupNotificationId = 1995; static const iosInitializationSettings = DarwinInitializationSettings(); diff --git a/lib/features/push_notification/presentation/notification/local_notification_manager.dart b/lib/features/push_notification/presentation/notification/local_notification_manager.dart index 1bdedaf7f..2458f1007 100644 --- a/lib/features/push_notification/presentation/notification/local_notification_manager.dart +++ b/lib/features/push_notification/presentation/notification/local_notification_manager.dart @@ -151,6 +151,10 @@ class LocalNotificationManager { ); } + Future removeNotification(String id) { + return _localNotificationsPlugin.cancel(id.hashCode); + } + void groupPushNotification() async { if (Platform.isIOS) { return; @@ -170,7 +174,7 @@ class LocalNotificationManager { ); await _localNotificationsPlugin.show( - 1995, + LocalNotificationConfig.groupNotificationId, null, null, LocalNotificationConfig.instance.generateNotificationDetails( @@ -181,6 +185,17 @@ class LocalNotificationManager { } } + void removeGroupPushNotification() async { + final activeNotifications = await _localNotificationsPlugin + .resolvePlatformSpecificImplementation() + ?.getActiveNotifications(); + log('LocalNotificationManager::removeGroupPushNotification():activeNotifications: ${activeNotifications?.length}'); + if (activeNotifications == null || activeNotifications.length <= 1) { + log('LocalNotificationManager::groupPushNotification():canceled'); + await _localNotificationsPlugin.cancel(LocalNotificationConfig.groupNotificationId); + } + } + Future recreateStreamController() { if (localNotificationsController.isClosed) { localNotificationsController = StreamController.broadcast();