From 20c1f1c295a8a9bf3a761bc07c876579aedee9f7 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jul 2024 09:40:12 +0700 Subject: [PATCH] TF-2871 Block notification on folders `Sent`, `Outbox`, `Drafts`, `Spam` and `Trash` --- ios/Podfile.lock | 2 +- ios/TwakeCore/Jmap/JmapClient.swift | 18 ++++- ios/TwakeCore/Jmap/Model/Email/Email.swift | 1 + ios/TwakeCore/Jmap/Utils/JmapConstants.swift | 3 +- .../Model/KeychainSharingSession.swift | 7 +- ios/TwakeMailNSE/NotificationService.swift | 66 ++++++++++++------- .../reloadable/reloadable_controller.dart | 60 ++++++++++++----- .../authorization_interceptors.dart | 4 +- .../update_authentication_account_state.dart | 11 +++- .../update_account_cache_interactor.dart | 40 +++++------ .../presentation/mailbox_controller.dart | 30 ++++++++- .../mailbox_dashboard_controller.dart | 2 - .../keychain_sharing_session_extension.dart | 6 +- .../keychain/keychain_sharing_session.dart | 6 ++ .../credential/credential_bindings.dart | 4 +- .../bindings/network/network_bindings.dart | 2 + lib/main/utils/ios_sharing_manager.dart | 55 +++++++++++++++- model/lib/extensions/mailbox_extension.dart | 12 ++++ model/lib/mailbox/presentation_mailbox.dart | 2 +- model/lib/oidc/token_oidc.dart | 3 + 20 files changed, 253 insertions(+), 81 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fd2a6e5260..42d69184a5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -323,7 +323,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 558b1da7d65afeb996fd5c814332f013234ece4e FirebaseMessaging: e345b219fd15d325f0cf2fef28cb8ce00d851b3f fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_appauth: 1ce438877bc111c5d8f42da47729909290624886 flutter_downloader: b7301ae057deadd4b1650dc7c05375f10ff12c39 flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e diff --git a/ios/TwakeCore/Jmap/JmapClient.swift b/ios/TwakeCore/Jmap/JmapClient.swift index b7dd4edb86..b2c9173b9e 100644 --- a/ios/TwakeCore/Jmap/JmapClient.swift +++ b/ios/TwakeCore/Jmap/JmapClient.swift @@ -68,6 +68,11 @@ class JmapClient { accountId: String, onComplete: @escaping ([Email], [Error]) -> Void) { guard hasMoreChanges, let sinceState = currentSinceState else { + if !self.totalListEmails.isEmpty { + let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails) + self.totalListEmails = sortedListEmails + } + return onComplete(self.totalListEmails, self.listErrors) } @@ -85,8 +90,7 @@ class JmapClient { if let response = data.parsing(methodName: JmapConstants.EMAIL_GET_METHOD_NAME, methodCallId: "c1") { if let listEmail = response.list, !listEmail.isEmpty { - let sortedListEmails = self.sortListEmails(currentListEmails: listEmail) - self.totalListEmails.append(contentsOf: sortedListEmails) + self.totalListEmails.append(contentsOf: listEmail) } self.hasMoreChanges = response.hasMoreChanges ?? false self.currentSinceState = response.newState @@ -97,6 +101,11 @@ class JmapClient { self.hasMoreChanges = false self.currentSinceState = nil + if !self.totalListEmails.isEmpty { + let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails) + self.totalListEmails = sortedListEmails + } + onComplete(self.totalListEmails, self.listErrors) } }, @@ -105,6 +114,11 @@ class JmapClient { self.hasMoreChanges = false self.currentSinceState = nil + if !self.totalListEmails.isEmpty { + let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails) + self.totalListEmails = sortedListEmails + } + onComplete(self.totalListEmails, self.listErrors) } ) diff --git a/ios/TwakeCore/Jmap/Model/Email/Email.swift b/ios/TwakeCore/Jmap/Model/Email/Email.swift index b546c41873..36aa6cf683 100644 --- a/ios/TwakeCore/Jmap/Model/Email/Email.swift +++ b/ios/TwakeCore/Jmap/Model/Email/Email.swift @@ -6,6 +6,7 @@ struct Email: Codable { let preview: String? let from: [EmailAddress]? let receivedAt: String? + let mailboxIds: [String: Bool]? func getSenderName() -> String? { if (from == nil || from?.isEmpty == true) { diff --git a/ios/TwakeCore/Jmap/Utils/JmapConstants.swift b/ios/TwakeCore/Jmap/Utils/JmapConstants.swift index 05d8677984..26f3a5b80a 100644 --- a/ios/TwakeCore/Jmap/Utils/JmapConstants.swift +++ b/ios/TwakeCore/Jmap/Utils/JmapConstants.swift @@ -14,7 +14,8 @@ class JmapConstants { "subject", "preview", "from", - "receivedAt" + "receivedAt", + "mailboxIds" ] static let EMAIL_ID = "email_id" diff --git a/ios/TwakeMailNSE/Model/KeychainSharingSession.swift b/ios/TwakeMailNSE/Model/KeychainSharingSession.swift index 4567d9cc5d..4ac23ce332 100644 --- a/ios/TwakeMailNSE/Model/KeychainSharingSession.swift +++ b/ios/TwakeMailNSE/Model/KeychainSharingSession.swift @@ -11,6 +11,7 @@ struct KeychainSharingSession: Codable { let basicAuth: String? let tokenEndpoint: String? let oidcScopes: [String]? + let mailboxIdsBlockNotification: [String]? } extension KeychainSharingSession { @@ -25,7 +26,8 @@ extension KeychainSharingSession { tokenOIDC: self.tokenOIDC, basicAuth: self.basicAuth, tokenEndpoint: self.tokenEndpoint, - oidcScopes: self.oidcScopes + oidcScopes: self.oidcScopes, + mailboxIdsBlockNotification: self.mailboxIdsBlockNotification ) } @@ -45,7 +47,8 @@ extension KeychainSharingSession { ), basicAuth: self.basicAuth, tokenEndpoint: self.tokenEndpoint, - oidcScopes: self.oidcScopes + oidcScopes: self.oidcScopes, + mailboxIdsBlockNotification: self.mailboxIdsBlockNotification ) } diff --git a/ios/TwakeMailNSE/NotificationService.swift b/ios/TwakeMailNSE/NotificationService.swift index 7f4f360d1d..e61f93521a 100644 --- a/ios/TwakeMailNSE/NotificationService.swift +++ b/ios/TwakeMailNSE/NotificationService.swift @@ -85,31 +85,15 @@ class NotificationService: UNNotificationServiceExtension { newEmailDeliveryState: newEmailDeliveryState ) - if (emails.count > 1) { - for email in emails { - if (email.id == emails.last?.id) { - self.showModifiedNotification(title: email.getSenderName(), - subtitle: email.subject, - body: email.preview, - badgeCount: emails.count, - userInfo: [JmapConstants.EMAIL_ID : email.id]) - return self.notify() - } else { - self.showNewNotification(title: email.getSenderName(), - subtitle: email.subject, - body: email.preview, - badgeCount: emails.count, - notificationId: email.id, - userInfo: [JmapConstants.EMAIL_ID : email.id]) - } - } + let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? [] + + if (mailboxIdsBlockNotification.isEmpty) { + return self.showListNotification(emails: emails) } else { - self.showModifiedNotification(title: emails.first!.getSenderName(), - subtitle: emails.first!.subject, - body: emails.first!.preview, - badgeCount: 1, - userInfo: [JmapConstants.EMAIL_ID : emails.first!.id]) - return self.notify() + let emailFiltered = self.filterEmailsToPushNotification( + emails: emails, + mailboxIdsBlockNotification: mailboxIdsBlockNotification) + return self.showListNotification(emails: emailFiltered) } } } catch { @@ -121,7 +105,39 @@ class NotificationService: UNNotificationServiceExtension { ) } } - + + private func filterEmailsToPushNotification(emails: [Email], mailboxIdsBlockNotification: [String]) -> [Email] { + return emails.filter { email in + guard let mailboxIds = email.mailboxIds else { return true } + for id in mailboxIds.keys { + if mailboxIdsBlockNotification.contains(id) { + return false + } + } + return true + } + } + + private func showListNotification(emails: [Email]) { + for email in emails { + if (email.id == emails.last?.id) { + self.showModifiedNotification(title: email.getSenderName(), + subtitle: email.subject, + body: email.preview, + badgeCount: emails.count, + userInfo: [JmapConstants.EMAIL_ID : email.id]) + return self.notify() + } else { + self.showNewNotification(title: email.getSenderName(), + subtitle: email.subject, + body: email.preview, + badgeCount: emails.count, + notificationId: email.id, + userInfo: [JmapConstants.EMAIL_ID : email.id]) + } + } + } + private func showDefaultNotification(message: String) { self.modifiedContent?.title = InfoPlistReader(bundle: .app).bundleDisplayName self.modifiedContent?.body = message diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index 95f35d6a1f..040c8d3d6e 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -34,13 +34,17 @@ abstract class ReloadableController extends BaseController { void handleFailureViewState(Failure failure) { if (failure is GetCredentialFailure || failure is GetStoredTokenOidcFailure || - failure is GetAuthenticatedAccountFailure || - failure is UpdateAccountCacheFailure) { + failure is GetAuthenticatedAccountFailure) { logError('$runtimeType::handleFailureViewState():Failure = $failure'); goToLogin(); } else if (failure is GetSessionFailure) { logError('$runtimeType::handleFailureViewState():Failure = $failure'); _handleGetSessionFailure(failure.exception); + } else if (failure is UpdateAccountCacheFailure) { + logError('$runtimeType::handleFailureViewState():Failure = $failure'); + _handleUpdateAccountCacheCompleted( + session: failure.session, + apiUrl: failure.apiUrl); } else { super.handleFailureViewState(failure); } @@ -50,25 +54,20 @@ abstract class ReloadableController extends BaseController { void handleSuccessViewState(Success success) { if (success is GetCredentialViewState) { log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); - _setDataToInterceptors( - baseUrl: success.baseUrl.origin, - userName: success.userName, - password: success.password); - getSessionAction(); + _handleGetCredentialSuccess(success); } else if (success is GetStoredTokenOidcSuccess) { log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); - _setDataToInterceptors( - baseUrl: success.baseUrl.toString(), - tokenOIDC: success.tokenOidc, - oidcConfiguration: success.oidcConfiguration); - getSessionAction(); + _handleGetStoredTokenOidcSuccess(success); } else if (success is GetSessionSuccess) { log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); - updateAccountCache(success.session); + updateAccountCache( + session: success.session, + baseUrl: dynamicUrlInterceptors.baseUrl); } else if (success is UpdateAccountCacheSuccess) { log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); - dynamicUrlInterceptors.changeBaseUrl(success.apiUrl); - handleReloaded(success.session); + _handleUpdateAccountCacheCompleted( + session: success.session, + apiUrl: success.apiUrl); } else { super.handleSuccessViewState(success); } @@ -85,6 +84,27 @@ abstract class ReloadableController extends BaseController { consumeState(_getAuthenticatedAccountInteractor.execute()); } + void _handleGetCredentialSuccess(GetCredentialViewState success) { + _setDataToInterceptors( + baseUrl: success.baseUrl.origin, + userName: success.userName, + password: success.password); + getSessionAction(); + } + + void _handleGetStoredTokenOidcSuccess(GetStoredTokenOidcSuccess success) { + _setDataToInterceptors( + baseUrl: success.baseUrl.toString(), + tokenOIDC: success.tokenOidc, + oidcConfiguration: success.oidcConfiguration); + getSessionAction(); + } + + void _handleUpdateAccountCacheCompleted({required Session session, String? apiUrl}) { + dynamicUrlInterceptors.changeBaseUrl(apiUrl); + handleReloaded(session); + } + void _setDataToInterceptors({ required String baseUrl, UserName? userName, @@ -131,7 +151,13 @@ abstract class ReloadableController extends BaseController { } } - void updateAccountCache(Session session) { - consumeState(_updateAccountCacheInteractor.execute(session)); + void updateAccountCache({ + required Session session, + String? baseUrl + }) { + consumeState(_updateAccountCacheInteractor.execute( + session: session, + baseUrl: baseUrl + )); } } \ No newline at end of file diff --git a/lib/features/login/data/network/interceptors/authorization_interceptors.dart b/lib/features/login/data/network/interceptors/authorization_interceptors.dart index 581b1c1aae..85b2078c3b 100644 --- a/lib/features/login/data/network/interceptors/authorization_interceptors.dart +++ b/lib/features/login/data/network/interceptors/authorization_interceptors.dart @@ -48,11 +48,11 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { _token = newToken; _configOIDC = newConfig; _authenticationType = AuthenticationType.oidc; - log('AuthorizationInterceptors::setTokenAndAuthorityOidc: TokenId = ${newToken?.tokenIdHash}'); + log('AuthorizationInterceptors::setTokenAndAuthorityOidc: INITIAL_TOKEN = ${newToken?.token} | EXPIRED_TIME = ${newToken?.expiredTime}'); } void _updateNewToken(TokenOIDC newToken) { - log('AuthorizationInterceptors::_updateNewToken: TokenId = ${newToken.tokenIdHash}'); + log('AuthorizationInterceptors::_updateNewToken: NEW_TOKEN = ${newToken.token} | EXPIRED_TIME = ${newToken.expiredTime}'); _token = newToken; } diff --git a/lib/features/login/domain/state/update_authentication_account_state.dart b/lib/features/login/domain/state/update_authentication_account_state.dart index f4a2f56080..b4de9636f6 100644 --- a/lib/features/login/domain/state/update_authentication_account_state.dart +++ b/lib/features/login/domain/state/update_authentication_account_state.dart @@ -17,6 +17,15 @@ class UpdateAccountCacheSuccess extends UIState { } class UpdateAccountCacheFailure extends FeatureFailure { + final Session session; + final String apiUrl; - UpdateAccountCacheFailure(dynamic exception) : super(exception: exception); + UpdateAccountCacheFailure({ + required this.session, + required this.apiUrl, + dynamic exception, + }) : super(exception: exception); + + @override + List get props => [session, apiUrl, ...super.props]; } \ No newline at end of file diff --git a/lib/features/login/domain/usecases/update_account_cache_interactor.dart b/lib/features/login/domain/usecases/update_account_cache_interactor.dart index b92dcb7fb7..239fb72060 100644 --- a/lib/features/login/domain/usecases/update_account_cache_interactor.dart +++ b/lib/features/login/domain/usecases/update_account_cache_interactor.dart @@ -1,39 +1,26 @@ 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:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:model/account/authentication_type.dart'; -import 'package:model/account/personal_account.dart'; import 'package:model/extensions/personal_account_extension.dart'; import 'package:model/extensions/session_extension.dart'; import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; -import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; import 'package:tmail_ui_user/features/login/domain/state/update_authentication_account_state.dart'; class UpdateAccountCacheInteractor { final AccountRepository _accountRepository; - final CredentialRepository _credentialRepository; - UpdateAccountCacheInteractor( - this._accountRepository, - this._credentialRepository); + UpdateAccountCacheInteractor(this._accountRepository); - Stream> execute(Session session) async* { + Stream> execute({required Session session, String? baseUrl}) async* { + final apiUrl = _getQualifiedApiUrl(session: session, baseUrl: baseUrl); + log('UpdateAccountCacheInteractor::execute: ApiUrl = $apiUrl'); try{ yield Right(UpdatingAccountCache()); - final futureValue = await Future.wait([ - _credentialRepository.getBaseUrl(), - _accountRepository.getCurrentAccount(), - ], eagerError: true); - - final baseUrl = futureValue[0] as Uri; - final currentAccount = futureValue[1] as PersonalAccount; - final apiUrl = session.getQualifiedApiUrl( - baseUrl: currentAccount.authenticationType == AuthenticationType.basic - ? baseUrl.origin - : baseUrl.toString()); + final currentAccount = await _accountRepository.getCurrentAccount(); await _accountRepository.setCurrentAccount( currentAccount.fromAccount( @@ -46,7 +33,20 @@ class UpdateAccountCacheInteractor { session: session, apiUrl: apiUrl)); } catch(e) { - yield Left(UpdateAccountCacheFailure(e)); + yield Left(UpdateAccountCacheFailure( + session: session, + apiUrl: apiUrl, + exception: e + )); + } + } + + String _getQualifiedApiUrl({required Session session, String? baseUrl}) { + try { + return session.getQualifiedApiUrl(baseUrl: baseUrl); + } catch (e) { + logError('UpdateAccountCacheInteractor::_getQualifiedApiUrl:Exception = $e'); + return ''; } } } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 25247f7891..8438d62549 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -84,6 +84,7 @@ import 'package:tmail_ui_user/main/routes/dialog_router.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; +import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; class MailboxController extends BaseMailboxController with MailboxActionHandlerMixin { @@ -97,6 +98,8 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM final SubscribeMultipleMailboxInteractor _subscribeMultipleMailboxInteractor; final CreateDefaultMailboxInteractor _createDefaultMailboxInteractor; + IOSSharingManager? _iosSharingManager; + final currentSelectMode = SelectMode.INACTIVE.obs; final _activeScrollTop = RxBool(false); final _activeScrollBottom = RxBool(true); @@ -206,6 +209,9 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM _handleCreateDefaultFolderIfMissing(mailboxDashBoardController.mapDefaultMailboxIdByRole); _handleDataFromNavigationRouter(); mailboxDashBoardController.getSpamReportBanner(); + if (PlatformInfo.isIOS) { + _updateMailboxIdsBlockNotificationToKeychain(success.mailboxList); + } } else if (success is RefreshChangesAllMailboxSuccess) { _selectSelectedMailboxDefault(); mailboxDashBoardController.refreshSpamReportBanner(); @@ -1115,7 +1121,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM curve: Curves.fastOutSlowIn); } - void _handleGetAllMailboxSuccess(GetAllMailboxSuccess success) async { + Future _handleGetAllMailboxSuccess(GetAllMailboxSuccess success) async { currentMailboxState = success.currentMailboxState; log('MailboxController::_handleGetAllMailboxSuccess:currentMailboxState: $currentMailboxState'); final listMailboxDisplayed = success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes; @@ -1127,6 +1133,28 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM _setOutboxMailbox(); } + Future _updateMailboxIdsBlockNotificationToKeychain(List mailboxes) async { + _iosSharingManager = getBinding(); + final accountId = mailboxDashBoardController.accountId.value; + if (accountId == null || _iosSharingManager == null || mailboxes.isEmpty) { + logError('MailboxController::_updateMailboxIdsBlockNotificationToKeychain: AccountId = $accountId | IosSharingManager = $_iosSharingManager | Mailboxes = ${mailboxes.length}'); + return; + } + + if (await _iosSharingManager!.isExistMailboxIdsBlockNotificationInKeyChain(accountId)) { + return; + } + + final mailboxIdsBlockNotification = mailboxes + .where((presentationMailbox) => presentationMailbox.pushNotificationDeactivated && presentationMailbox.mailboxId != null) + .map((presentationMailbox) => presentationMailbox.mailboxId!) + .toList(); + log('MailboxController::_updateMailboxIdsBlockNotificationToKeychain:MailboxIdsBlockNotification = $mailboxIdsBlockNotification'); + _iosSharingManager!.updateMailboxIdsBlockNotificationInKeyChain( + accountId: accountId, + mailboxIds: mailboxIdsBlockNotification); + } + void _handleRefreshChangesAllMailboxSuccess(RefreshChangesAllMailboxSuccess success) async { currentMailboxState = success.currentMailboxState; log('MailboxController::_handleRefreshChangesAllMailboxSuccess:currentMailboxState: $currentMailboxState'); diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index b7cbe88902..ae3e63f8a9 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -517,8 +517,6 @@ class MailboxDashBoardController extends ReloadableController { void _handleSessionFromArguments(Session session) { log('MailboxDashBoardController::_handleSession:'); - updateAccountCache(session); - _setUpComponentsFromSession(session); if (PlatformInfo.isWeb) { diff --git a/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart b/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart index 16bf658522..d2eb1bd2ae 100644 --- a/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart +++ b/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart @@ -1,20 +1,22 @@ +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_session.dart'; extension KeychainSharingSessionExtension on KeychainSharingSession { - KeychainSharingSession updating({String? emailState}) { + KeychainSharingSession updating({String? emailState, List? mailboxIdsBlockNotification}) { return KeychainSharingSession( accountId: accountId, userName: userName, authenticationType: authenticationType, apiUrl: apiUrl, - emailState: emailState ?? emailState, + emailState: emailState ?? this.emailState, emailDeliveryState: emailDeliveryState, tokenOIDC: tokenOIDC, basicAuth: basicAuth, tokenEndpoint: tokenEndpoint, oidcScopes: oidcScopes, + mailboxIdsBlockNotification: mailboxIdsBlockNotification ?? this.mailboxIdsBlockNotification, ); } } \ No newline at end of file diff --git a/lib/features/push_notification/data/keychain/keychain_sharing_session.dart b/lib/features/push_notification/data/keychain/keychain_sharing_session.dart index 2847726b06..a8d8391754 100644 --- a/lib/features/push_notification/data/keychain/keychain_sharing_session.dart +++ b/lib/features/push_notification/data/keychain/keychain_sharing_session.dart @@ -1,14 +1,17 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/http/converter/account_id_converter.dart'; +import 'package:jmap_dart_client/http/converter/mailbox_id_converter.dart'; import 'package:jmap_dart_client/http/converter/user_name_converter.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:model/account/authentication_type.dart'; import 'package:model/oidc/token_oidc.dart'; part 'keychain_sharing_session.g.dart'; +@MailboxIdConverter() @UserNameConverter() @AccountIdConverter() @JsonSerializable(includeIfNull: false, explicitToJson: true) @@ -23,6 +26,7 @@ class KeychainSharingSession with EquatableMixin { String? basicAuth; String? tokenEndpoint; List? oidcScopes; + List? mailboxIdsBlockNotification; KeychainSharingSession({ required this.accountId, @@ -35,6 +39,7 @@ class KeychainSharingSession with EquatableMixin { this.basicAuth, this.tokenEndpoint, this.oidcScopes, + this.mailboxIdsBlockNotification, }); factory KeychainSharingSession.fromJson(Map json) => _$KeychainSharingSessionFromJson(json); @@ -53,5 +58,6 @@ class KeychainSharingSession with EquatableMixin { basicAuth, tokenEndpoint, oidcScopes, + mailboxIdsBlockNotification, ]; } \ No newline at end of file diff --git a/lib/main/bindings/credential/credential_bindings.dart b/lib/main/bindings/credential/credential_bindings.dart index 35cf854748..10e5a7e938 100644 --- a/lib/main/bindings/credential/credential_bindings.dart +++ b/lib/main/bindings/credential/credential_bindings.dart @@ -61,9 +61,7 @@ class CredentialBindings extends InteractorsBindings { Get.find(), Get.find() )); - Get.put(UpdateAccountCacheInteractor( - Get.find(), - Get.find())); + Get.put(UpdateAccountCacheInteractor(Get.find())); } @override diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index d5823d2bed..343a6e8293 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -23,6 +23,7 @@ import 'package:tmail_ui_user/features/login/data/network/dns_service.dart'; import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart'; import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_auth_plugin/app_auth_plugin.dart'; +import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/network/spam_report_api.dart'; @@ -80,6 +81,7 @@ class NetworkBindings extends Bindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); } diff --git a/lib/main/utils/ios_sharing_manager.dart b/lib/main/utils/ios_sharing_manager.dart index 62367fb43e..aad25f60bb 100644 --- a/lib/main/utils/ios_sharing_manager.dart +++ b/lib/main/utils/ios_sharing_manager.dart @@ -4,13 +4,16 @@ import 'dart:convert'; import 'package:core/utils/app_logger.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/account/authentication_type.dart'; import 'package:model/account/personal_account.dart'; +import 'package:model/extensions/mailbox_extension.dart'; import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/login/data/local/authentication_info_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/oidc_configuration_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart'; +import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; import 'package:tmail_ui_user/features/push_notification/data/extensions/keychain_sharing_session_extension.dart'; @@ -24,6 +27,7 @@ class IOSSharingManager { final AuthenticationInfoCacheManager _authenticationInfoCacheManager; final OidcConfigurationCacheManager _oidcConfigurationCacheManager; final OIDCHttpClient _oidcHttpClient; + final MailboxCacheManager _mailboxCacheManager; IOSSharingManager( this._keychainSharingManager, @@ -32,6 +36,7 @@ class IOSSharingManager { this._authenticationInfoCacheManager, this._oidcConfigurationCacheManager, this._oidcHttpClient, + this._mailboxCacheManager, ); bool _validateToSaveKeychain(PersonalAccount personalAccount) { @@ -79,6 +84,10 @@ class IOSSharingManager { final tokenRecords = await _getTokenEndpointAndScopes(); + final mailboxIdsBlockNotification = await _getMailboxIdsBlockNotification( + accountId: personalAccount.accountId!, + userName: personalAccount.userName!); + final keychainSharingSession = KeychainSharingSession( accountId: personalAccount.accountId!, userName: personalAccount.userName!, @@ -90,7 +99,7 @@ class IOSSharingManager { basicAuth: credentialInfo, tokenEndpoint: tokenRecords?.tokenEndpoint, oidcScopes: tokenRecords?.scopes, - ); + mailboxIdsBlockNotification: mailboxIdsBlockNotification); await _keychainSharingManager.save(keychainSharingSession); @@ -184,4 +193,48 @@ class IOSSharingManager { final newKeychain = keychainSharingStored.updating(emailState: newEmailState); await _keychainSharingManager.save(newKeychain); } + + Future isExistMailboxIdsBlockNotificationInKeyChain(AccountId accountId) async { + try { + final keychainSharingStored = await getKeychainSharingSession(accountId); + return keychainSharingStored?.mailboxIdsBlockNotification?.isNotEmpty == true; + } catch (e) { + logError('IOSSharingManager::getMailboxIdsBlockNotificationInKeyChain:Exception = $e'); + return false; + } + } + + Future updateMailboxIdsBlockNotificationInKeyChain({ + required AccountId accountId, + required List mailboxIds + }) async { + try { + final keychainSharingStored = await getKeychainSharingSession(accountId); + if (keychainSharingStored == null) { + return; + } + final newKeychain = keychainSharingStored.updating(mailboxIdsBlockNotification: mailboxIds); + await _keychainSharingManager.save(newKeychain); + } catch (e) { + logError('IOSSharingManager::updateMailboxIdsBlockNotificationInKeyChain: Exception = $e'); + } + } + + Future?> _getMailboxIdsBlockNotification({ + required AccountId accountId, + required UserName userName, + }) async { + try { + final mailboxesCache = await _mailboxCacheManager.getAllMailbox(accountId, userName); + final listMailboxIdBlockNotification = mailboxesCache + .where((mailbox) => mailbox.pushNotificationDeactivated && mailbox.id != null) + .map((mailbox) => mailbox.id!) + .toList(); + log('IOSSharingManager::_getMailboxIdsBlockNotification(): CACHE_MAILBOX_LIST = $listMailboxIdBlockNotification'); + return listMailboxIdBlockNotification; + } catch (e) { + logError('IOSSharingManager::_getMailboxIdsBlockNotification:Exception: $e'); + return null; + } + } } \ No newline at end of file diff --git a/model/lib/extensions/mailbox_extension.dart b/model/lib/extensions/mailbox_extension.dart index a39976b584..712f84701c 100644 --- a/model/lib/extensions/mailbox_extension.dart +++ b/model/lib/extensions/mailbox_extension.dart @@ -6,6 +6,18 @@ extension MailboxExtension on Mailbox { bool hasRole() => role != null && role!.value.isNotEmpty; + bool get isSpam => role == PresentationMailbox.roleSpam; + + bool get isTrash => role == PresentationMailbox.roleTrash; + + bool get isDrafts => role == PresentationMailbox.roleDrafts; + + bool get isSent => role == PresentationMailbox.roleSent; + + bool get isOutbox => name?.name == PresentationMailbox.outboxRole || role == PresentationMailbox.roleOutbox; + + bool get pushNotificationDeactivated => isOutbox || isSent || isDrafts || isTrash || isSpam; + PresentationMailbox toPresentationMailbox() { return PresentationMailbox( id!, diff --git a/model/lib/mailbox/presentation_mailbox.dart b/model/lib/mailbox/presentation_mailbox.dart index 4d34e9a44b..52e24c4365 100644 --- a/model/lib/mailbox/presentation_mailbox.dart +++ b/model/lib/mailbox/presentation_mailbox.dart @@ -14,7 +14,7 @@ class PresentationMailbox with EquatableMixin { static const String templatesRole= 'templates'; static const String outboxRole = 'outbox'; static const String draftsRole = 'drafts'; - static const String spamRole = 'spam'; + static const String spamRole = 'junk'; static const String archiveRole = 'archive'; static const String recoveredRole = 'restored messages'; diff --git a/model/lib/oidc/token_oidc.dart b/model/lib/oidc/token_oidc.dart index 50fd2798fb..05afa3448e 100644 --- a/model/lib/oidc/token_oidc.dart +++ b/model/lib/oidc/token_oidc.dart @@ -1,4 +1,5 @@ +import 'package:core/utils/app_logger.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:model/oidc/converter/token_id_converter.dart'; @@ -39,6 +40,8 @@ extension TokenOIDCExtension on TokenOIDC { bool get isExpired { if (expiredTime != null) { final now = DateTime.now(); + log('TokenOIDC::isExpired(): TIME_NOW: $now'); + log('TokenOIDC::isExpired(): EXPIRED_DATE: $expiredTime'); return expiredTime!.isBefore(now); } return false;