From 501d4d38d9e365be92976f32ba325951628da24c Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 11 Jul 2024 09:46:23 +0700 Subject: [PATCH] TF-2871 Handle click notification to open detailed email on iOS --- core/lib/utils/html/html_utils.dart | 3 +- ...51-push-notification-click-logic-on-ios.md | 4 +- ios/Runner.xcodeproj/project.pbxproj | 6 - ios/Runner/AppDelegate.swift | 45 ++++--- ios/TwakeCore/Extensions/DateExtensions.swift | 4 + .../Extensions/IntegerExtensions.swift | 11 -- .../AuthenticationInterceptor.swift | 9 +- ios/TwakeCore/Utils/CoreUtils.swift | 3 + ios/TwakeMailTests/DateConversionTests.swift | 45 ++++++- lib/features/base/base_controller.dart | 28 ++--- .../reloadable/reloadable_controller.dart | 110 ++++++++---------- .../home/presentation/home_controller.dart | 49 ++------ .../authorization_interceptors.dart | 60 +++++----- .../update_authentication_account_state.dart | 19 ++- .../update_account_cache_interactor.dart | 52 +++++++++ ...ate_authentication_account_interactor.dart | 31 ----- .../mailbox_dashboard_controller.dart | 64 +++++----- .../model/preview_email_arguments.dart | 15 --- .../manage_account_dashboard_controller.dart | 2 +- .../work_manager/sending_email_worker.dart | 2 +- .../controller/fcm_message_controller.dart | 2 +- .../presentation/services/fcm_receiver.dart | 30 ----- .../presentation/services/fcm_service.dart | 13 --- lib/main/bindings/core/core_bindings.dart | 4 + .../credential/credential_bindings.dart | 6 +- lib/main/utils/ios_notification_manager.dart | 79 +++++++++++++ lib/main/utils/ios_sharing_manager.dart | 41 +++---- model/lib/extensions/session_extension.dart | 2 + model/lib/oidc/token_oidc.dart | 3 - .../presentation/home_controller_test.dart | 10 +- .../presentation/login_controller_test.dart | 10 +- .../mailbox_dashboard_controller_test.dart | 8 +- 32 files changed, 416 insertions(+), 354 deletions(-) delete mode 100644 ios/TwakeCore/Extensions/IntegerExtensions.swift create mode 100644 lib/features/login/domain/usecases/update_account_cache_interactor.dart delete mode 100644 lib/features/login/domain/usecases/update_authentication_account_interactor.dart delete mode 100644 lib/features/mailbox_dashboard/presentation/model/preview_email_arguments.dart create mode 100644 lib/main/utils/ios_notification_manager.dart diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index 828e3ac15..63a212074 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -30,7 +30,6 @@ class HtmlUtils { static const unregisterDropListener = ( script: ''' - console.log("unregisterDropListener"); const editor = document.querySelector(".note-editable"); const newEditor = editor.cloneNode(true); editor.parentNode.replaceChild(newEditor, editor);''', @@ -76,12 +75,12 @@ class HtmlUtils { required String base64Data, required String mimeType }) { + log('HtmlUtils::convertBase64ToImageResourceData:'); mimeType = validateHtmlImageResourceMimeType(mimeType); if (!base64Data.endsWith('==')) { base64Data.append('=='); } final imageResource = 'data:$mimeType;base64,$base64Data'; - log('HtmlUtils::convertBase64ToImageResourceData:imageResource: $imageResource'); return imageResource; } diff --git a/docs/adr/0051-push-notification-click-logic-on-ios.md b/docs/adr/0051-push-notification-click-logic-on-ios.md index bba139562..bd7202c69 100644 --- a/docs/adr/0051-push-notification-click-logic-on-ios.md +++ b/docs/adr/0051-push-notification-click-logic-on-ios.md @@ -33,7 +33,7 @@ Brief the logic flows when clicking notifications: We use `FlutterMethodChannel` to pass `UserInfo` from iOS native code to Flutter dart code ```swift - self.notificationInteractionChannel?.invokeMethod("current_email_id_in_notification_click_on_foreground", arguments: response.notification.request.content.userInfo) + self.notificationInteractionChannel?.invokeMethod("current_email_id_in_notification_click_when_app_foreground_or_background", arguments: response.notification.request.content.userInfo) ``` 2. Terminated state @@ -52,7 +52,7 @@ Save it in a `remoteNotificationPayload` variable in memory and use `FlutterMeth ```swift self.notificationInteractionChannel?.setMethodCallHandler { (call, result) in switch call.method { - case "current_email_id_in_notification_click": + case "current_email_id_in_notification_click_when_app_terminated": result(self.remoteNotificationPayload) self.remoteNotificationPayload = nil default: diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index bf1d15d48..ccb0f1f88 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -21,8 +21,6 @@ F522E8862C0EE8B600DDA35B /* CoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8852C0EE8B600DDA35B /* CoreUtils.swift */; }; F522E8872C0EE8B600DDA35B /* CoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8852C0EE8B600DDA35B /* CoreUtils.swift */; }; F522E8882C0EE8B600DDA35B /* CoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8852C0EE8B600DDA35B /* CoreUtils.swift */; }; - F522E88A2C0F117900DDA35B /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8892C0F117900DDA35B /* IntegerExtensions.swift */; }; - F522E88B2C0F117900DDA35B /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8892C0F117900DDA35B /* IntegerExtensions.swift */; }; F52F993027FD6EB900346091 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52F992F27FD6EB900346091 /* ShareViewController.swift */; }; F52F993327FD6EB900346091 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F52F993127FD6EB900346091 /* MainInterface.storyboard */; }; F52F993727FD6EB900346091 /* TeamMailShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = F52F992D27FD6EB900346091 /* TeamMailShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -139,7 +137,6 @@ B2EAFF659572E6B9F5AFAAF8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; F522E87E2C0EE23400DDA35B /* AuthenticationSSOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSSOTests.swift; sourceTree = ""; }; F522E8852C0EE8B600DDA35B /* CoreUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUtils.swift; sourceTree = ""; }; - F522E8892C0F117900DDA35B /* IntegerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerExtensions.swift; sourceTree = ""; }; F52F992D27FD6EB900346091 /* TeamMailShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TeamMailShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; F52F992F27FD6EB900346091 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; F52F993227FD6EB900346091 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; @@ -446,7 +443,6 @@ children = ( F5E7D8782B38763B0009BB8A /* DateExtensions.swift */, F5E7D87B2B38764F0009BB8A /* StringExtensions.swift */, - F522E8892C0F117900DDA35B /* IntegerExtensions.swift */, ); path = Extensions; sourceTree = ""; @@ -748,7 +744,6 @@ files = ( F5E7D87C2B38764F0009BB8A /* StringExtensions.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - F522E88A2C0F117900DDA35B /* IntegerExtensions.swift in Sources */, F5E7D8792B38763B0009BB8A /* DateExtensions.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, F522E8862C0EE8B600DDA35B /* CoreUtils.swift in Sources */, @@ -787,7 +782,6 @@ F53D1E7F2B2E3C2600051FD0 /* JmapConstants.swift in Sources */, F5E7D8822B3876F60009BB8A /* AuthenticationCredential.swift in Sources */, F5D4EA032B2ABF090090DDFC /* NotificationService.swift in Sources */, - F522E88B2C0F117900DDA35B /* IntegerExtensions.swift in Sources */, F522E8872C0EE8B600DDA35B /* CoreUtils.swift in Sources */, F53D1E8A2B2E4BB700051FD0 /* TokenOidc.swift in Sources */, F53D1E862B2E401B00051FD0 /* JmapRequestGenerator.swift in Sources */, diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 0c13269e0..faa9d613d 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -8,7 +8,7 @@ import flutter_local_notifications @objc class AppDelegate: FlutterAppDelegate { var notificationInteractionChannel: FlutterMethodChannel? - var initialNotificationInfo: Any? + var currentEmailId: String? override func application( _ application: UIApplication, @@ -17,7 +17,12 @@ import flutter_local_notifications GeneratedPluginRegistrant.register(with: self) createNotificationInteractionChannel() - initialNotificationInfo = launchOptions?[.remoteNotification] + + if let payload = launchOptions?[.remoteNotification] as? [AnyHashable : Any], + let emailId = payload[JmapConstants.EMAIL_ID] as? String, + !emailId.isEmpty { + currentEmailId = emailId + } if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate @@ -90,9 +95,7 @@ import flutter_local_notifications TwakeLogger.shared.log(message: "AppDelegate::userNotificationCenter::willPresent:newBadgeCount: \(newBadgeCount)") updateAppBadger(newBadgeCount: newBadgeCount) } - if let emailId = notification.request.content.userInfo[JmapConstants.EMAIL_ID] as? String, - !emailId.isEmpty, - !isAppForegroundActive() { + if validateDisplayPushNotification(userInfo: notification.request.content.userInfo) { completionHandler([.alert, .badge, .sound]) } else { completionHandler([]) @@ -105,12 +108,24 @@ import flutter_local_notifications let newBadgeCount = currentBadgeCount > 0 ? currentBadgeCount - 1 : 0 updateAppBadger(newBadgeCount: newBadgeCount) - if let emailId = response.notification.request.content.userInfo[JmapConstants.EMAIL_ID] as? String { - self.notificationInteractionChannel?.invokeMethod("openEmail", arguments: emailId) + + let userInfo = response.notification.request.content.userInfo + + if let emailId = userInfo[JmapConstants.EMAIL_ID] as? String, !emailId.isEmpty { + self.notificationInteractionChannel?.invokeMethod( + CoreUtils.CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_FOREGROUND_OR_BACKGROUND, + arguments: emailId) } completionHandler() } + + private func validateDisplayPushNotification(userInfo: [AnyHashable : Any]) -> Bool { + if let emailId = userInfo[JmapConstants.EMAIL_ID] as? String, !emailId.isEmpty, UIApplication.shared.applicationState != .active { + return true + } + return false + } } extension AppDelegate { @@ -134,25 +149,21 @@ extension AppDelegate { } } - private func isAppForegroundActive() -> Bool { - return UIApplication.shared.applicationState == .active - } - private func createNotificationInteractionChannel() { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController self.notificationInteractionChannel = FlutterMethodChannel( - name: "notification_interaction_channel", + name: CoreUtils.NOTIFICATION_INTERACTION_CHANNEL_NAME, binaryMessenger: controller.binaryMessenger ) self.notificationInteractionChannel?.setMethodCallHandler { (call, result) in switch call.method { - case "getInitialNotificationInfo": - result(self.initialNotificationInfo) - self.initialNotificationInfo = nil - default: - break + case CoreUtils.CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_TERMINATED: + result(self.currentEmailId) + self.currentEmailId = nil + default: + break } } } diff --git a/ios/TwakeCore/Extensions/DateExtensions.swift b/ios/TwakeCore/Extensions/DateExtensions.swift index 9ee091784..305cbde93 100644 --- a/ios/TwakeCore/Extensions/DateExtensions.swift +++ b/ios/TwakeCore/Extensions/DateExtensions.swift @@ -11,4 +11,8 @@ extension Date { dateFormatter.locale = Locale(identifier: CoreUtils.EN_US_POSIX_LOCALE) return dateFormatter.string(from: self) } + + func adding(seconds: Int) -> Date { + return self.addingTimeInterval(TimeInterval(seconds)) + } } diff --git a/ios/TwakeCore/Extensions/IntegerExtensions.swift b/ios/TwakeCore/Extensions/IntegerExtensions.swift deleted file mode 100644 index 1efc2d464..000000000 --- a/ios/TwakeCore/Extensions/IntegerExtensions.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -extension Int { - func convertMillisecondsToDate() -> Date { - return Date(timeIntervalSince1970: TimeInterval(self) / 1000) - } - - func convertMillisecondsToISO8601String() -> String { - return convertMillisecondsToDate().convertDateToISO8601String() - } -} diff --git a/ios/TwakeCore/Network/Interceptor/AuthenticationInterceptor.swift b/ios/TwakeCore/Network/Interceptor/AuthenticationInterceptor.swift index 4619113ec..1b7ecb780 100644 --- a/ios/TwakeCore/Network/Interceptor/AuthenticationInterceptor.swift +++ b/ios/TwakeCore/Network/Interceptor/AuthenticationInterceptor.swift @@ -41,9 +41,12 @@ class AuthenticationInterceptor: RequestInterceptor { } let newRefreshToken = tokenResponse.refreshToken ?? authenticationSSO.refreshToken - let expireTime = tokenResponse.expiresTime != nil - ? tokenResponse.expiresTime!.convertMillisecondsToISO8601String() - : nil + + var expireTime: String? = nil + + if (tokenResponse.expiresTime != nil) { + expireTime = CoreUtils.shared.getCurrentDate().adding(seconds: tokenResponse.expiresTime!).convertDateToISO8601String() + } self.authentication = AuthenticationSSO( type: AuthenticationType.oidc, diff --git a/ios/TwakeCore/Utils/CoreUtils.swift b/ios/TwakeCore/Utils/CoreUtils.swift index f0d1bde46..254d08cda 100644 --- a/ios/TwakeCore/Utils/CoreUtils.swift +++ b/ios/TwakeCore/Utils/CoreUtils.swift @@ -5,6 +5,9 @@ class CoreUtils { static let ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS" static let EN_US_POSIX_LOCALE = "en_US_POSIX" + static let NOTIFICATION_INTERACTION_CHANNEL_NAME = "notification_interaction_channel" + static let CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_FOREGROUND_OR_BACKGROUND = "current_email_id_in_notification_click_when_app_foreground_or_background" + static let CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_TERMINATED = "current_email_id_in_notification_click_when_app_terminated" func getCurrentDate() -> Date { if #available(iOS 15, *) { diff --git a/ios/TwakeMailTests/DateConversionTests.swift b/ios/TwakeMailTests/DateConversionTests.swift index 51c4c58b5..0953d0dbc 100644 --- a/ios/TwakeMailTests/DateConversionTests.swift +++ b/ios/TwakeMailTests/DateConversionTests.swift @@ -5,25 +5,39 @@ import XCTest class DateConversionTests: XCTestCase { func testConvertValidISO8601StringToDate() { + // Arrange let validDateString = "2024-05-20T22:54:57.958" + // Act let date = validDateString.convertISO8601StringToDate() + + // Assert XCTAssertNotNil(date, "Date should not be nil") + // Arrange let calendar = Calendar.current let expectedComponents = DateComponents(year: 2024, month: 5, day: 20, hour: 22, minute: 54, second: 57, nanosecond: 958000000) + + // Act let expectedDate = calendar.date(from: expectedComponents) + + // Assert XCTAssertEqual(date, expectedDate, "Converted date does not match expected date") } func testInvalidISO8601String() { + // Arrange let invalidDateString = "Invalid Date String" + // Act let date = invalidDateString.convertISO8601StringToDate() + + // Assert XCTAssertNil(date, "The conversion should return nil for an invalid date string.") } func testConvertValidDateToISO8601String() { + // Arrange let validDate = Calendar.current.date( from: DateComponents( year: 2024, @@ -35,11 +49,38 @@ class DateConversionTests: XCTestCase { nanosecond: 958000000 ) ) - - let expectedDateString = "2024-05-20T22:54:57.958" + + // Act let validDateString = validDate!.convertDateToISO8601String() + // Assert XCTAssertEqual(validDateString, expectedDateString, "Converted date string does not match expected date string") } + + func testAddingSeconds() { + // Arrange + let initialDate = Date() + let secondsToAdd = 60 + let expectedDate = initialDate.addingTimeInterval(TimeInterval(secondsToAdd)) + + // Act + let resultDate = initialDate.adding(seconds: secondsToAdd) + + // Assert + XCTAssertEqual(resultDate, expectedDate, "The date after adding seconds should be correct.") + } + + func testAddingNegativeSeconds() { + // Arrange + let initialDate = Date() + let secondsToSubtract = -60 + let expectedDate = initialDate.addingTimeInterval(TimeInterval(secondsToSubtract)) + + // Act + let resultDate = initialDate.adding(seconds: secondsToSubtract) + + // Assert + XCTAssertEqual(resultDate, expectedDate, "The date after subtracting seconds should be correct.") + } } diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index e44350eed..9ca39a37e 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -106,7 +106,7 @@ abstract class BaseController extends GetxController (failure) { if (failure is FeatureFailure) { final exception = _performFilterExceptionInError(failure.exception); - logError('BaseController::onData:exception: $exception'); + logError('$runtimeType::onData:exception: $exception'); if (exception != null) { handleExceptionAction(failure: failure, exception: exception); } else { @@ -120,7 +120,7 @@ abstract class BaseController extends GetxController } void onError(Object error, StackTrace stackTrace) { - logError('BaseController::onError():error: $error | stackTrace: $stackTrace'); + logError('$runtimeType::onError():error: $error | stackTrace: $stackTrace'); final exception = _performFilterExceptionInError(error); if (exception != null) { handleExceptionAction(exception: exception); @@ -132,7 +132,7 @@ abstract class BaseController extends GetxController void onDone() {} Exception? _performFilterExceptionInError(dynamic error) { - logError('BaseController::_performFilterExceptionInError(): $error'); + logError('$runtimeType::_performFilterExceptionInError(): $error'); if (error is NoNetworkError || error is ConnectionTimeout || error is InternalServerError) { if (PlatformInfo.isWeb && currentOverlayContext != null && currentContext != null) { appToast.showToastMessage( @@ -157,7 +157,7 @@ abstract class BaseController extends GetxController void handleErrorViewState(Object error, StackTrace stackTrace) {} void handleExceptionAction({Failure? failure, Exception? exception}) { - logError('BaseController::handleExceptionAction():failure: $failure | exception: $exception'); + logError('$runtimeType::handleExceptionAction():failure: $failure | exception: $exception'); if (exception is ConnectionError) { if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( @@ -183,7 +183,7 @@ abstract class BaseController extends GetxController } void handleFailureViewState(Failure failure) async { - logError('BaseController::handleFailureViewState(): ${failure.runtimeType}'); + logError('$runtimeType::handleFailureViewState():Failure = $failure'); if (failure is LogoutOidcFailure) { if (_isFcmEnabled) { _getStoredFirebaseRegistrationFromCache(); @@ -197,7 +197,7 @@ abstract class BaseController extends GetxController } void handleSuccessViewState(Success success) async { - log('BaseController::handleSuccessViewState(): ${success.runtimeType}'); + log('$runtimeType::handleSuccessViewState():Success = ${success.runtimeType}'); if (success is LogoutOidcSuccess) { if (_isFcmEnabled) { _getStoredFirebaseRegistrationFromCache(); @@ -214,7 +214,7 @@ abstract class BaseController extends GetxController void startFpsMeter() { FpsManager().start(); fpsCallback = (fpsInfo) { - log('BaseController::startFpsMeter(): $fpsInfo'); + log('$runtimeType::startFpsMeter(): $fpsInfo'); }; if (fpsCallback != null) { FpsManager().addFpsCallback(fpsCallback!); @@ -234,7 +234,7 @@ abstract class BaseController extends GetxController requireCapability(session!, accountId!, [tmailContactCapabilityIdentifier]); TMailAutoCompleteBindings().dependencies(); } catch (e) { - logError('BaseController::injectAutoCompleteBindings(): exception: $e'); + logError('$runtimeType::injectAutoCompleteBindings(): exception: $e'); } } @@ -243,7 +243,7 @@ abstract class BaseController extends GetxController requireCapability(session!, accountId!, [CapabilityIdentifier.jmapMdn]); MdnInteractorBindings().dependencies(); } catch(e) { - logError('BaseController::injectMdnBindings(): exception: $e'); + logError('$runtimeType::injectMdnBindings(): exception: $e'); } } @@ -252,7 +252,7 @@ abstract class BaseController extends GetxController requireCapability(session!, accountId!, [capabilityForward]); ForwardingInteractorsBindings().dependencies(); } catch(e) { - logError('BaseController::injectForwardBindings(): exception: $e'); + logError('$runtimeType::injectForwardBindings(): exception: $e'); } } @@ -261,14 +261,14 @@ abstract class BaseController extends GetxController requireCapability(session!, accountId!, [capabilityRuleFilter]); EmailRulesInteractorBindings().dependencies(); } catch(e) { - logError('BaseController::injectRuleFilterBindings(): exception: $e'); + logError('$runtimeType::injectRuleFilterBindings(): exception: $e'); } } Future injectFCMBindings(Session? session, AccountId? accountId) async { try { requireCapability(session!, accountId!, [FirebaseCapability.fcmIdentifier]); - log('BaseController::injectFCMBindings: fcmAvailable = ${AppConfig.fcmAvailable}'); + log('$runtimeType::injectFCMBindings: fcmAvailable = ${AppConfig.fcmAvailable}'); if (AppConfig.fcmAvailable) { final mapEnvData = Map.from(dotenv.env); await AppUtils.loadFcmConfigFileToEnv(currentMapEnvData: mapEnvData); @@ -285,7 +285,7 @@ abstract class BaseController extends GetxController throw NotSupportFCMException(); } } catch(e) { - logError('BaseController::injectFCMBindings(): exception: $e'); + logError('$runtimeType::injectFCMBindings(): exception: $e'); } } @@ -348,7 +348,7 @@ abstract class BaseController extends GetxController } Future clearDataAndGoToLoginPage() async { - log('BaseController::clearDataAndGoToLoginPage:'); + log('$runtimeType::clearDataAndGoToLoginPage:'); await clearAllData(); goToLogin(); } diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index 71ecafff7..95f35d6a1 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -6,16 +6,18 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; -import 'package:model/extensions/session_extension.dart'; +import 'package:model/account/password.dart'; +import 'package:model/oidc/oidc_configuration.dart'; +import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; -import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/home/domain/usecases/get_session_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_authenticated_account_state.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/state/update_authentication_account_state.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/update_authentication_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_interactors_bindings.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; @@ -26,16 +28,18 @@ import 'package:tmail_ui_user/main/utils/message_toast_utils.dart'; abstract class ReloadableController extends BaseController { final GetSessionInteractor _getSessionInteractor = Get.find(); final GetAuthenticatedAccountInteractor _getAuthenticatedAccountInteractor = Get.find(); - final UpdateAuthenticationAccountInteractor _updateAuthenticationAccountInteractor = Get.find(); + final UpdateAccountCacheInteractor _updateAccountCacheInteractor = Get.find(); @override void handleFailureViewState(Failure failure) { if (failure is GetCredentialFailure || failure is GetStoredTokenOidcFailure || - failure is GetAuthenticatedAccountFailure) { - log('ReloadableController::handleFailureViewState(): failure: $failure'); + failure is GetAuthenticatedAccountFailure || + failure is UpdateAccountCacheFailure) { + logError('$runtimeType::handleFailureViewState():Failure = $failure'); goToLogin(); } else if (failure is GetSessionFailure) { + logError('$runtimeType::handleFailureViewState():Failure = $failure'); _handleGetSessionFailure(failure.exception); } else { super.handleFailureViewState(failure); @@ -45,11 +49,26 @@ abstract class ReloadableController extends BaseController { @override void handleSuccessViewState(Success success) { if (success is GetCredentialViewState) { - _handleGetCredentialSuccess(success); - } else if (success is GetSessionSuccess) { - _handleGetSessionSuccess(success); + log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); + _setDataToInterceptors( + baseUrl: success.baseUrl.origin, + userName: success.userName, + password: success.password); + getSessionAction(); } else if (success is GetStoredTokenOidcSuccess) { - _handleGetStoredTokenOIDCSuccess(success); + log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); + _setDataToInterceptors( + baseUrl: success.baseUrl.toString(), + tokenOIDC: success.tokenOidc, + oidcConfiguration: success.oidcConfiguration); + getSessionAction(); + } else if (success is GetSessionSuccess) { + log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); + updateAccountCache(success.session); + } else if (success is UpdateAccountCacheSuccess) { + log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); + dynamicUrlInterceptors.changeBaseUrl(success.apiUrl); + handleReloaded(success.session); } else { super.handleSuccessViewState(success); } @@ -66,22 +85,25 @@ abstract class ReloadableController extends BaseController { consumeState(_getAuthenticatedAccountInteractor.execute()); } - void _setUpInterceptors(GetCredentialViewState credentialViewState) { - dynamicUrlInterceptors.setJmapUrl(credentialViewState.baseUrl.origin); - dynamicUrlInterceptors.changeBaseUrl(credentialViewState.baseUrl.origin); - authorizationInterceptors.setBasicAuthorization( - credentialViewState.userName, - credentialViewState.password, - ); - authorizationIsolateInterceptors.setBasicAuthorization( - credentialViewState.userName, - credentialViewState.password, - ); - } + void _setDataToInterceptors({ + required String baseUrl, + UserName? userName, + Password? password, + TokenOIDC? tokenOIDC, + OIDCConfiguration? oidcConfiguration + }) { + dynamicUrlInterceptors.setJmapUrl(baseUrl); + dynamicUrlInterceptors.changeBaseUrl(baseUrl); - void _handleGetCredentialSuccess(GetCredentialViewState credentialViewState) { - _setUpInterceptors(credentialViewState); - getSessionAction(); + if (userName != null && password != null) { + authorizationInterceptors.setBasicAuthorization(userName, password); + authorizationIsolateInterceptors.setBasicAuthorization(userName, password); + } + + if (tokenOIDC != null && oidcConfiguration != null) { + authorizationInterceptors.setTokenAndAuthorityOidc(newToken: tokenOIDC, newConfig: oidcConfiguration); + authorizationIsolateInterceptors.setTokenAndAuthorityOidc(newToken: tokenOIDC, newConfig: oidcConfiguration); + } } void getSessionAction() { @@ -98,50 +120,18 @@ abstract class ReloadableController extends BaseController { clearDataAndGoToLoginPage(); } - void _handleGetSessionSuccess(GetSessionSuccess success) { - final session = success.session; - final personalAccount = session.personalAccount; - final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl); - if (apiUrl.isNotEmpty) { - dynamicUrlInterceptors.changeBaseUrl(apiUrl); - updateAuthenticationAccount(session, personalAccount.accountId, session.username); - handleReloaded(session); - } else { - clearDataAndGoToLoginPage(); - } - } - void handleReloaded(Session session) {} - void _handleGetStoredTokenOIDCSuccess(GetStoredTokenOidcSuccess tokenOidcSuccess) { - _setUpInterceptorsOidc(tokenOidcSuccess); - getSessionAction(); - } - - void _setUpInterceptorsOidc(GetStoredTokenOidcSuccess tokenOidcSuccess) { - dynamicUrlInterceptors.setJmapUrl(tokenOidcSuccess.baseUrl.toString()); - dynamicUrlInterceptors.changeBaseUrl(tokenOidcSuccess.baseUrl.toString()); - authorizationInterceptors.setTokenAndAuthorityOidc( - newToken: tokenOidcSuccess.tokenOidc, - newConfig: tokenOidcSuccess.oidcConfiguration); - authorizationIsolateInterceptors.setTokenAndAuthorityOidc( - newToken: tokenOidcSuccess.tokenOidc, - newConfig: tokenOidcSuccess.oidcConfiguration); - } - void injectVacationBindings(Session? session, AccountId? accountId) { try { requireCapability(session!, accountId!, [CapabilityIdentifier.jmapVacationResponse]); VacationInteractorsBindings().dependencies(); } catch(e) { - logError('ReloadableController::injectVacationBindings(): exception: $e'); + logError('$runtimeType::injectVacationBindings(): exception: $e'); } } - void updateAuthenticationAccount(Session session, AccountId accountId, UserName userName) { - final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl); - if (apiUrl.isNotEmpty) { - consumeState(_updateAuthenticationAccountInteractor.execute(accountId, apiUrl, userName)); - } + void updateAccountCache(Session session) { + consumeState(_updateAccountCacheInteractor.execute(session)); } } \ No newline at end of file diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart index c7a40a7d9..4f6758cdd 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -2,11 +2,8 @@ import 'package:core/utils/platform_info.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:get/get.dart'; -import 'package:jmap_dart_client/jmap/core/id.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/email_address.dart'; -import 'package:model/account/personal_account.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/email_content_type.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; @@ -21,12 +18,11 @@ import 'package:tmail_ui_user/features/cleanup/domain/usecases/cleanup_email_cac import 'package:tmail_ui_user/features/cleanup/domain/usecases/cleanup_recent_login_url_cache_interactor.dart'; import 'package:tmail_ui_user/features/cleanup/domain/usecases/cleanup_recent_login_username_interactor.dart'; import 'package:tmail_ui_user/features/cleanup/domain/usecases/cleanup_recent_search_cache_interactor.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/preview_email_arguments.dart'; -import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_receiver.dart'; import 'package:tmail_ui_user/main/routes/app_routes.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/email_receive_manager.dart'; +import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart'; class HomeController extends ReloadableController { final CleanupEmailCacheInteractor _cleanupEmailCacheInteractor; @@ -35,6 +31,8 @@ class HomeController extends ReloadableController { final CleanupRecentLoginUrlCacheInteractor _cleanupRecentLoginUrlCacheInteractor; final CleanupRecentLoginUsernameCacheInteractor _cleanupRecentLoginUsernameCacheInteractor; + IOSNotificationManager? _iosNotificationManager; + HomeController( this._cleanupEmailCacheInteractor, this._emailReceiveManager, @@ -43,9 +41,6 @@ class HomeController extends ReloadableController { this._cleanupRecentLoginUsernameCacheInteractor, ); - PersonalAccount? currentAccount; - EmailId? _emailIdPreview; - @override void onInit() { if (PlatformInfo.isMobile) { @@ -53,7 +48,7 @@ class HomeController extends ReloadableController { _registerReceivingSharingIntent(); } if (PlatformInfo.isIOS) { - _handleIOSDataMessage(); + _registerNotificationClickOnIOS(); } super.onInit(); } @@ -66,20 +61,9 @@ class HomeController extends ReloadableController { @override void handleReloaded(Session session) { - if (_emailIdPreview != null) { - popAndPush( - RouteUtils.generateNavigationRoute(AppRoutes.dashboard), - arguments: PreviewEmailArguments( - session: session, - emailId: _emailIdPreview! - ) - ); - } else { - popAndPush( - RouteUtils.generateNavigationRoute(AppRoutes.dashboard), - arguments: session - ); - } + pushAndPopAll( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: session); } void _initFlutterDownloader() { @@ -90,7 +74,7 @@ class HomeController extends ReloadableController { static void downloadCallback(String id, DownloadTaskStatus status, int progress) {} - void _cleanupCache() async { + Future _cleanupCache() async { await HiveCacheConfig.instance.onUpgradeDatabase(cachingManager); await Future.wait([ @@ -98,7 +82,7 @@ class HomeController extends ReloadableController { _cleanupRecentSearchCacheInteractor.execute(RecentSearchCleanupRule()), _cleanupRecentLoginUrlCacheInteractor.execute(RecentLoginUrlCleanupRule()), _cleanupRecentLoginUsernameCacheInteractor.execute(RecentLoginUsernameCleanupRule()), - ]).then((value) => getAuthenticatedAccountAction()); + ], eagerError: true).then((_) => getAuthenticatedAccountAction()); } void _registerReceivingSharingIntent() { @@ -117,17 +101,8 @@ class HomeController extends ReloadableController { _emailReceiveManager.receivingFileSharingStream.listen(_emailReceiveManager.setPendingFileInfo); } - Future _handleIOSDataMessage() async { - if (Get.arguments is EmailId) { - _emailIdPreview = Get.arguments; - } else { - final notificationInfo = await FcmReceiver.instance.getIOSInitialNotificationInfo(); - if (notificationInfo != null && notificationInfo.containsKey('email_id')) { - final emailId = notificationInfo['email_id'] as String?; - if (emailId?.isNotEmpty == true) { - _emailIdPreview = EmailId(Id(emailId!)); - } - } - } + void _registerNotificationClickOnIOS() { + _iosNotificationManager = getBinding(); + _iosNotificationManager?.listenClickNotification(); } } \ 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 34d21b5db..581b1c1aa 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: INITIAL_TOKEN = ${newToken?.token} | EXPIRED_TIME = ${newToken?.expiredTime}'); + log('AuthorizationInterceptors::setTokenAndAuthorityOidc: TokenId = ${newToken?.tokenIdHash}'); } void _updateNewToken(TokenOIDC newToken) { - log('AuthorizationInterceptors::_updateNewToken: NEW_TOKEN = ${newToken.token} | EXPIRED_TIME = ${newToken.expiredTime}'); + log('AuthorizationInterceptors::_updateNewToken: TokenId = ${newToken.tokenIdHash}'); _token = newToken; } @@ -76,13 +76,13 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { case AuthenticationType.none: break; } - log('AuthorizationInterceptors::onRequest(): URL = ${options.uri} | HEADER = ${options.headers} | DATA = ${options.data} | METHOD = ${options.method}'); + log('AuthorizationInterceptors::onRequest(): URL = ${options.uri} | DATA = ${options.data}'); super.onRequest(options, handler); } @override void onError(DioError err, ErrorInterceptorHandler handler) async { - logError('AuthorizationInterceptors::onError(): TOKEN = ${_token?.expiredTime} | DIO_ERROR = $err | METHOD = ${err.requestOptions.method}'); + logError('AuthorizationInterceptors::onError(): DIO_ERROR = $err'); try { final requestOptions = err.requestOptions; final extraInRequest = requestOptions.extra; @@ -92,24 +92,29 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { responseStatusCode: err.response?.statusCode, tokenOIDC: _token )) { - log('AuthorizationInterceptors::onError:_validateToRefreshToken'); + log('AuthorizationInterceptors::onError: Perform get New Token'); final newTokenOidc = PlatformInfo.isIOS - ? await _handleRefreshTokenOnIOSPlatform() - : await _handleRefreshTokenOnOtherPlatform(); + ? await _getNewTokenForIOSPlatform() + : await _getNewTokenForOtherPlatform(); if (newTokenOidc.token == _token?.token) { - log('AuthorizationInterceptors::onError: TokenOIDC duplicated'); + log('AuthorizationInterceptors::onError: Token duplicated'); return super.onError(err, handler); } - _updateNewToken(newTokenOidc); + final personalAccount = await _updateCurrentAccount(tokenOIDC: newTokenOidc); + + if (PlatformInfo.isIOS) { + await _iosSharingManager.saveKeyChainSharingSession(personalAccount); + } + isRetryRequest = true; } else if (validateToRetryTheRequestWithNewToken( authHeader: requestOptions.headers[HttpHeaders.authorizationHeader], tokenOIDC: _token )) { - log('AuthorizationInterceptors::onError:validateToRetryTheRequestWithNewToken'); + log('AuthorizationInterceptors::onError: Request using old token'); isRetryRequest = true; } else { return super.onError(err, handler); @@ -117,7 +122,7 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { if (isRetryRequest) { if (extraInRequest.containsKey(FileUploader.uploadAttachmentExtraKey)) { - log('AuthorizationInterceptors::onError: Perform upload attachment request'); + log('AuthorizationInterceptors::onError: Retry upload request with TokenId = ${_token?.tokenIdHash}'); final uploadExtra = extraInRequest[FileUploader.uploadAttachmentExtraKey]; requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token); @@ -136,7 +141,7 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { return handler.resolve(response); } else { - log('AuthorizationInterceptors::onError: Perform normal request'); + log('AuthorizationInterceptors::onError: Retry request with TokenId = ${_token?.tokenIdHash}'); requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token); final response = await _dio.fetch(requestOptions); @@ -231,13 +236,11 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { Future _getTokenInKeychain(TokenOIDC currentTokenOidc) async { final currentAccount = await _accountCacheManager.getCurrentAccount(); - log('AuthorizationInterceptors::_getTokenInKeychain:currentAccount: $currentAccount'); if (currentAccount.accountId == null) { return null; } final keychainSharingSession = await _iosSharingManager.getKeychainSharingSession(currentAccount.accountId!); - log('AuthorizationInterceptors::_getTokenInKeychain:keychainSharingSession: $keychainSharingSession'); if (keychainSharingSession == null) { return null; } @@ -250,36 +253,29 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { return null; } - Future _invokeRefreshTokenFromServer() async { - final newToken = await _authenticationClient.refreshingTokensOIDC( + Future _invokeRefreshTokenFromServer() { + log('AuthorizationInterceptors::_invokeRefreshTokenFromServer:'); + return _authenticationClient.refreshingTokensOIDC( _configOIDC!.clientId, _configOIDC!.redirectUrl, _configOIDC!.discoveryUrl, _configOIDC!.scopes, _token!.refreshToken ); - log('AuthorizationInterceptors::_invokeRefreshTokenFromServer:newToken: $newToken'); - return newToken; } - Future _handleRefreshTokenOnIOSPlatform() async { - final keychainToken = await _getTokenInKeychain(_token!); - - if (keychainToken == null) { - final newToken = await _invokeRefreshTokenFromServer(); - final newAccount = await _updateCurrentAccount(tokenOIDC: newToken); - await _iosSharingManager.saveKeyChainSharingSession(newAccount); - return newToken; + Future _getNewTokenForIOSPlatform() async { + final tokenInKeychain = await _getTokenInKeychain(_token!); + log('AuthorizationInterceptors::_handleRefreshTokenOnIOSPlatform: KeychainTokenId = ${tokenInKeychain?.tokenIdHash} | isTokenExpired = ${_isTokenExpired(tokenInKeychain)}'); + if (tokenInKeychain == null || _isTokenExpired(tokenInKeychain)) { + return _invokeRefreshTokenFromServer(); } else { - await _updateCurrentAccount(tokenOIDC: keychainToken); - return keychainToken; + return tokenInKeychain; } } - Future _handleRefreshTokenOnOtherPlatform() async { - final newToken = await _invokeRefreshTokenFromServer(); - await _updateCurrentAccount(tokenOIDC: newToken); - return newToken; + Future _getNewTokenForOtherPlatform() { + return _invokeRefreshTokenFromServer(); } void clear() { 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 c5d1931ed..f4a2f5608 100644 --- a/lib/features/login/domain/state/update_authentication_account_state.dart +++ b/lib/features/login/domain/state/update_authentication_account_state.dart @@ -1,11 +1,22 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; -class UpdateAuthenticationAccountLoading extends LoadingState {} +class UpdatingAccountCache extends LoadingState {} -class UpdateAuthenticationAccountSuccess extends UIState {} +class UpdateAccountCacheSuccess extends UIState { + final Session session; + final String apiUrl; -class UpdateAuthenticationAccountFailure extends FeatureFailure { + UpdateAccountCacheSuccess({ + required this.session, + required this.apiUrl}); - UpdateAuthenticationAccountFailure(dynamic exception) : super(exception: exception); + @override + List get props => [session, apiUrl]; +} + +class UpdateAccountCacheFailure extends FeatureFailure { + + UpdateAccountCacheFailure(dynamic exception) : super(exception: exception); } \ 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 new file mode 100644 index 000000000..b92dcb7fb --- /dev/null +++ b/lib/features/login/domain/usecases/update_account_cache_interactor.dart @@ -0,0 +1,52 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.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); + + Stream> execute(Session session) async* { + 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()); + + await _accountRepository.setCurrentAccount( + currentAccount.fromAccount( + accountId: session.accountId, + apiUrl: apiUrl, + userName: session.username + )); + + yield Right(UpdateAccountCacheSuccess( + session: session, + apiUrl: apiUrl)); + } catch(e) { + yield Left(UpdateAccountCacheFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/login/domain/usecases/update_authentication_account_interactor.dart b/lib/features/login/domain/usecases/update_authentication_account_interactor.dart deleted file mode 100644 index d12cfd696..000000000 --- a/lib/features/login/domain/usecases/update_authentication_account_interactor.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:core/presentation/state/failure.dart'; -import 'package:core/presentation/state/success.dart'; -import 'package:dartz/dartz.dart'; -import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/user_name.dart'; -import 'package:model/extensions/personal_account_extension.dart'; -import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; -import 'package:tmail_ui_user/features/login/domain/state/update_authentication_account_state.dart'; - -class UpdateAuthenticationAccountInteractor { - final AccountRepository _accountRepository; - - UpdateAuthenticationAccountInteractor(this._accountRepository); - - Stream> execute(AccountId accountId, String apiUrl, UserName userName) async* { - try{ - yield Right(UpdateAuthenticationAccountLoading()); - final currentAccount = await _accountRepository.getCurrentAccount(); - await _accountRepository.setCurrentAccount( - currentAccount.fromAccount( - accountId: accountId, - apiUrl: apiUrl, - userName: userName - ) - ); - yield Right(UpdateAuthenticationAccountSuccess()); - } catch(e) { - yield Left(UpdateAuthenticationAccountFailure(e)); - } - } -} \ No newline at end of file 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 1c7103e45..b7cbe8890 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -94,7 +94,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/comp import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/preview_email_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/refresh_action_view_event.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; @@ -153,6 +152,7 @@ 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/email_receive_manager.dart'; +import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart'; import 'package:uuid/uuid.dart'; class MailboxDashBoardController extends ReloadableController { @@ -197,6 +197,7 @@ class MailboxDashBoardController extends ReloadableController { GetMailboxStateToRefreshInteractor? _getMailboxStateToRefreshInteractor; DeleteMailboxStateToRefreshInteractor? _deleteMailboxStateToRefreshInteractor; GetAutoCompleteInteractor? _getAutoCompleteInteractor; + IOSNotificationManager? _iosNotificationManager; final scaffoldKey = GlobalKey(); final selectedMailbox = Rxn(); @@ -234,6 +235,8 @@ class MailboxDashBoardController extends ReloadableController { late StreamSubscription _emailContentStreamSubscription; late StreamSubscription _fileReceiveManagerStreamSubscription; + StreamSubscription? _currentEmailIdInNotificationIOSStreamSubscription; + final StreamController> _progressStateController = StreamController>.broadcast(); Stream> get progressState => _progressStateController.stream; @@ -272,7 +275,7 @@ class MailboxDashBoardController extends ReloadableController { ); @override - void onInit() async { + void onInit() { _registerStreamListener(); BackButtonInterceptor.add(_onBackButtonInterceptor, name: AppRoutes.dashboard); WidgetsBinding.instance.addPostFrameCallback((_) async { @@ -286,6 +289,9 @@ class MailboxDashBoardController extends ReloadableController { _registerPendingEmailAddress(); _registerPendingEmailContents(); _registerPendingFileInfo(); + if (PlatformInfo.isIOS) { + _registerPendingCurrentEmailIdInNotification(); + } _handleArguments(); super.onReady(); } @@ -349,7 +355,7 @@ class MailboxDashBoardController extends ReloadableController { } else if (success is GetAppDashboardConfigurationSuccess) { appGridDashboardController.handleShowAppDashboard(success.linagoraApplications); } else if(success is GetEmailByIdSuccess) { - _moveToEmailDetailedView(success); + openEmailDetailedView(success.email); } else if (success is StoreSendingEmailSuccess) { _handleStoreSendingEmailSuccess(success); } else if (success is GetAllSendingEmailSuccess) { @@ -392,7 +398,7 @@ class MailboxDashBoardController extends ReloadableController { } else if (failure is MarkAsMailboxReadFailure) { _markAsReadMailboxFailure(failure); } else if (failure is GetEmailByIdFailure) { - _handleGetEmailDetailedFailed(failure); + _handleGetEmailByIdFailure(failure); } else if (failure is RestoreDeletedMessageFailure) { _handleRestoreDeletedMessageFailed(); } else if (failure is GetRestoredDeletedMessageFailure) { @@ -439,6 +445,18 @@ class MailboxDashBoardController extends ReloadableController { }); } + void _registerPendingCurrentEmailIdInNotification() { + _iosNotificationManager = getBinding(); + _currentEmailIdInNotificationIOSStreamSubscription = _iosNotificationManager + ?.pendingCurrentEmailIdInNotification.stream + .listen((emailId) { + if (emailId != null) { + _iosNotificationManager?.clearPendingCurrentEmailId(); + _handleNotificationMessageFromEmailId(emailId); + } + }); + } + void _registerStreamListener() { progressState.listen((state) { viewStateMarkAsReadMailbox.value = state; @@ -455,7 +473,7 @@ class MailboxDashBoardController extends ReloadableController { _notificationManager.localNotificationStream.listen(_handleClickLocalNotificationOnForeground); } - void _handleClickLocalNotificationOnTerminated() async { + Future _handleClickNotificationOnAndroidInTerminated() async { _notificationManager.activatedNotificationClickedOnTerminate = true; final notificationResponse = await _notificationManager.getCurrentNotificationResponse(); log('MailboxDashBoardController::_handleClickLocalNotificationOnTerminated():payload: ${notificationResponse?.payload}'); @@ -464,13 +482,11 @@ class MailboxDashBoardController extends ReloadableController { void _handleArguments() { final arguments = Get.arguments; - log('MailboxDashBoardController::_getSessionCurrent(): arguments = $arguments'); + log('MailboxDashBoardController::_handleArguments():Arguments = ${arguments.runtimeType}'); if (arguments is Session) { _handleSessionFromArguments(arguments); } else if (arguments is MailtoArguments) { _handleMailtoURL(arguments); - } else if (arguments is PreviewEmailArguments) { - _handleOpenEmailAction(arguments); } else { dispatchRoute(DashboardRoutes.thread); reload(); @@ -501,11 +517,7 @@ class MailboxDashBoardController extends ReloadableController { void _handleSessionFromArguments(Session session) { log('MailboxDashBoardController::_handleSession:'); - updateAuthenticationAccount( - session, - session.personalAccount.accountId, - session.username - ); + updateAccountCache(session); _setUpComponentsFromSession(session); @@ -513,15 +525,15 @@ class MailboxDashBoardController extends ReloadableController { _handleComposerCache(); } - if (PlatformInfo.isMobile && !_notificationManager.isNotificationClickedOnTerminate) { - _handleClickLocalNotificationOnTerminated(); + if (PlatformInfo.isAndroid && !_notificationManager.isNotificationClickedOnTerminate) { + _handleClickNotificationOnAndroidInTerminated(); } else { dispatchRoute(DashboardRoutes.thread); } } void _setUpComponentsFromSession(Session session) { - final currentAccountId = session.personalAccount.accountId; + final currentAccountId = session.accountId; sessionCurrent = session; accountId.value = currentAccountId; @@ -546,13 +558,6 @@ class MailboxDashBoardController extends ReloadableController { _handleSessionFromArguments(arguments.session); } - void _handleOpenEmailAction(PreviewEmailArguments arguments) { - log('MailboxDashBoardController::_handleOpenEmailAction:arguments: $arguments'); - dispatchRoute(DashboardRoutes.waiting); - _handleSessionFromArguments(arguments.session); - _handleNotificationMessageFromEmailId(arguments.emailId); - } - void _getVacationResponse() { if (accountId.value != null && _getAllVacationInteractor != null) { consumeState(_getAllVacationInteractor!.execute(accountId.value!)); @@ -1794,14 +1799,7 @@ class MailboxDashBoardController extends ReloadableController { )); } - void _moveToEmailDetailedView(GetEmailByIdSuccess success) { - log('MailboxDashBoardController::_moveToEmailDetailedView(): ${success.email}'); - setSelectedEmail(success.email); - dispatchRoute(DashboardRoutes.emailDetailed); - } - - void _handleGetEmailDetailedFailed(GetEmailByIdFailure failure) { - logError('MailboxDashBoardController::_handleGetEmailDetailedFailed(): $failure'); + void _handleGetEmailByIdFailure(GetEmailByIdFailure failure) { dispatchRoute(DashboardRoutes.thread); } @@ -2552,6 +2550,10 @@ class MailboxDashBoardController extends ReloadableController { @override void onClose() { _emailReceiveManager.closeEmailReceiveManagerStream(); + if (PlatformInfo.isIOS) { + _iosNotificationManager?.dispose(); + _currentEmailIdInNotificationIOSStreamSubscription?.cancel(); + } _emailAddressStreamSubscription.cancel(); _emailContentStreamSubscription.cancel(); _fileReceiveManagerStreamSubscription.cancel(); diff --git a/lib/features/mailbox_dashboard/presentation/model/preview_email_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/preview_email_arguments.dart deleted file mode 100644 index 4ac7555ef..000000000 --- a/lib/features/mailbox_dashboard/presentation/model/preview_email_arguments.dart +++ /dev/null @@ -1,15 +0,0 @@ - -import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:tmail_ui_user/main/routes/router_arguments.dart'; - -class PreviewEmailArguments extends RouterArguments { - - final Session session; - final EmailId emailId; - - PreviewEmailArguments({required this.session, required this.emailId}); - - @override - List get props => [session, emailId]; -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart index acff137cb..40106ecbf 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart @@ -85,7 +85,7 @@ class ManageAccountDashBoardController extends ReloadableController { void handleReloaded(Session session) { log('ManageAccountDashBoardController::handleReloaded:'); sessionCurrent = session; - accountId.value = session.personalAccount.accountId; + accountId.value = session.accountId; _bindingInteractorForMenuItemView(sessionCurrent, accountId.value); _getVacationResponse(); _getParametersRouter(); diff --git a/lib/features/offline_mode/work_manager/sending_email_worker.dart b/lib/features/offline_mode/work_manager/sending_email_worker.dart index cc8064914..670aac94d 100644 --- a/lib/features/offline_mode/work_manager/sending_email_worker.dart +++ b/lib/features/offline_mode/work_manager/sending_email_worker.dart @@ -161,7 +161,7 @@ class SendingEmailWorker extends Worker { void _handleGetSessionSuccess(GetSessionSuccess success) async { _currentSession = success.session; - _currentAccountId = success.session.personalAccount.accountId; + _currentAccountId = success.session.accountId; final apiUrl = success.session.getQualifiedApiUrl(baseUrl: _dynamicUrlInterceptors?.jmapUrl); if (apiUrl.isNotEmpty && _currentSession != null && _currentAccountId != null) { _dynamicUrlInterceptors?.changeBaseUrl(apiUrl); 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 9c69a7153..c573fea73 100644 --- a/lib/features/push_notification/presentation/controller/fcm_message_controller.dart +++ b/lib/features/push_notification/presentation/controller/fcm_message_controller.dart @@ -270,7 +270,7 @@ class FcmMessageController extends FcmBaseController { _dynamicUrlInterceptors?.changeBaseUrl(apiUrl); _pushActionFromRemoteMessageBackground( - accountId: success.session.personalAccount.accountId, + accountId: success.session.accountId, userName: success.session.username, stateChange: stateChange, session: success.session); diff --git a/lib/features/push_notification/presentation/services/fcm_receiver.dart b/lib/features/push_notification/presentation/services/fcm_receiver.dart index 66ff6fdab..51fa43e66 100644 --- a/lib/features/push_notification/presentation/services/fcm_receiver.dart +++ b/lib/features/push_notification/presentation/services/fcm_receiver.dart @@ -1,9 +1,7 @@ - import 'package:core/utils/app_logger.dart'; import 'package:core/utils/broadcast_channel/broadcast_channel.dart'; import 'package:core/utils/platform_info.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/services.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_message_controller.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; @@ -23,7 +21,6 @@ class FcmReceiver { static FcmReceiver get instance => _instance; - static const notificationInteractionChannel = MethodChannel('notification_interaction_channel'); static const int MAX_COUNT_RETRY_TO_GET_FCM_TOKEN = 3; int _countRetryToGetFcmToken = 0; @@ -36,24 +33,11 @@ class FcmReceiver { if (PlatformInfo.isWeb) { _onMessageBroadcastChannel(); await _requestNotificationPermissionOnWeb(); - } else if (PlatformInfo.isIOS) { - _setUpIOSNotificationInteraction(); - await _onHandleFcmToken(); } else { await _onHandleFcmToken(); } } - void _setUpIOSNotificationInteraction() { - notificationInteractionChannel.setMethodCallHandler((call) async { - log('FcmReceiver::_setUpIOSNotificationInteraction:notificationInteractionChannel: $call'); - if (call.method == 'openEmail' && call.arguments is String) { - log('FcmReceiver::_setUpIOSNotificationInteraction:openEmail with id = ${call.arguments}'); - FcmService.instance.handleOpenEmailFromNotification(call.arguments); - } - }); - } - Future _requestNotificationPermissionOnWeb() async { NotificationSettings notificationSetting = await FirebaseMessaging.instance.getNotificationSettings(); log('FcmReceiver::_requestNotificationPermissionOnWeb: authorizationStatus = ${notificationSetting.authorizationStatus}'); @@ -118,18 +102,4 @@ class FcmReceiver { } }); } - - Future?> getIOSInitialNotificationInfo() async { - try { - final notificationInfo = await notificationInteractionChannel.invokeMethod('getInitialNotificationInfo'); - log('FcmReceiver::getIOSInitialNotificationInfo:notificationInfo: $notificationInfo'); - if (notificationInfo != null && notificationInfo is Map) { - return notificationInfo; - } - return null; - } catch (e) { - logError('FcmReceiver::getIOSInitialNotificationInfo: Exception: $e'); - return null; - } - } } \ 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 index 031b43d82..147bded91 100644 --- a/lib/features/push_notification/presentation/services/fcm_service.dart +++ b/lib/features/push_notification/presentation/services/fcm_service.dart @@ -4,12 +4,7 @@ import 'dart:convert'; import 'package:core/utils/app_logger.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/model/broadcast_message_event_data.dart'; -import 'package:tmail_ui_user/main/routes/app_routes.dart'; -import 'package:tmail_ui_user/main/routes/route_navigation.dart'; -import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:universal_html/html.dart' as html; class FcmService { @@ -63,14 +58,6 @@ class FcmService { fcmTokenStreamController = StreamController.broadcast(); } - void handleOpenEmailFromNotification(String emailId) { - log('FcmService::handleOpenEmailFromNotification:emailId: $emailId'); - popAndPush( - RouteUtils.generateNavigationRoute(AppRoutes.home), - arguments: EmailId(Id(emailId)) - ); - } - void closeStream() { foregroundMessageStreamController?.close(); backgroundMessageStreamController?.close(); diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index 4c337813c..069808895 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/base/before_unload_manager.dart'; import 'package:tmail_ui_user/features/sending_queue/presentation/utils/sending_queue_isolate_manager.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; +import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart'; import 'package:uuid/uuid.dart'; class CoreBindings extends Bindings { @@ -66,6 +67,9 @@ class CoreBindings extends Bindings { Get.put(PrintUtils()); Get.put(ApplicationManager(Get.find())); Get.put(BeforeUnloadManager()); + if (PlatformInfo.isIOS) { + Get.put(IOSNotificationManager()); + } } void _bindingIsolate() { diff --git a/lib/main/bindings/credential/credential_bindings.dart b/lib/main/bindings/credential/credential_bindings.dart index 82abf014d..35cf85474 100644 --- a/lib/main/bindings/credential/credential_bindings.dart +++ b/lib/main/bindings/credential/credential_bindings.dart @@ -27,7 +27,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_i import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_credential_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_token_oidc_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/update_authentication_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; @@ -61,7 +61,9 @@ class CredentialBindings extends InteractorsBindings { Get.find(), Get.find() )); - Get.put(UpdateAuthenticationAccountInteractor(Get.find())); + Get.put(UpdateAccountCacheInteractor( + Get.find(), + Get.find())); } @override diff --git a/lib/main/utils/ios_notification_manager.dart b/lib/main/utils/ios_notification_manager.dart new file mode 100644 index 000000000..d8dc83259 --- /dev/null +++ b/lib/main/utils/ios_notification_manager.dart @@ -0,0 +1,79 @@ + +import 'dart:async'; + +import 'package:core/utils/app_logger.dart'; +import 'package:flutter/services.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:model/model.dart'; +import 'package:rxdart/rxdart.dart'; + +class IOSNotificationManager { + + static const _notificationInteractionChannel = MethodChannel('notification_interaction_channel'); + + static const String CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_FOREGROUND_OR_BACKGROUND = 'current_email_id_in_notification_click_when_app_foreground_or_background'; + static const String CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_TERMINATED = 'current_email_id_in_notification_click_when_app_terminated'; + + BehaviorSubject _pendingCurrentEmailIdInNotification = BehaviorSubject.seeded(null); + BehaviorSubject get pendingCurrentEmailIdInNotification => _pendingCurrentEmailIdInNotification; + + StreamSubscription? _getCurrentEmailIdStreamSubscription; + + void listenClickNotification() { + try { + _notificationInteractionChannel.setMethodCallHandler((methodCall) async { + log('IOSNotificationManager::listenClickNotification: $methodCall'); + if (methodCall.method == CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_FOREGROUND_OR_BACKGROUND + && methodCall.arguments != null) { + final emailId = EmailId(Id(methodCall.arguments)); + setPendingCurrentEmailId(emailId); + } + }); + + _getCurrentEmailIdStreamSubscription = Stream.fromFuture(_getCurrentEmailIdInNotificationClick()).listen((emailId) { + log('IOSNotificationManager::listenClickNotification:_getCurrentEmailIdInNotificationClick:EmailId = ${emailId?.asString}'); + if (emailId != null) { + setPendingCurrentEmailId(emailId); + } + }); + } catch (e) { + logError('IOSNotificationManager::listenClickNotification:Exception = $e'); + } + } + + Future _getCurrentEmailIdInNotificationClick() async { + try { + log('IOSNotificationManager::_getCurrentEmailIdInNotificationClick: START'); + final emailId = await _notificationInteractionChannel.invokeMethod(CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_TERMINATED); + log('IOSNotificationManager::_getCurrentEmailIdInNotificationClick: END'); + if (emailId?.isNotEmpty == true) { + return EmailId(Id(emailId!)); + } else { + return null; + } + } catch (e) { + logError('IOSNotificationManager::getCurrentEmailIdInNotificationClick:Exception = $e'); + return null; + } + } + + void setPendingCurrentEmailId(EmailId emailId) async { + clearPendingCurrentEmailId(); + _pendingCurrentEmailIdInNotification.add(emailId); + } + + void clearPendingCurrentEmailId() { + if(_pendingCurrentEmailIdInNotification.isClosed) { + _pendingCurrentEmailIdInNotification = BehaviorSubject.seeded(null); + } else { + _pendingCurrentEmailIdInNotification.add(null); + } + } + + void dispose() { + _pendingCurrentEmailIdInNotification.close(); + _getCurrentEmailIdStreamSubscription?.cancel(); + _getCurrentEmailIdStreamSubscription = null; + } +} \ No newline at end of file diff --git a/lib/main/utils/ios_sharing_manager.dart b/lib/main/utils/ios_sharing_manager.dart index 5c40f21b2..62367fb43 100644 --- a/lib/main/utils/ios_sharing_manager.dart +++ b/lib/main/utils/ios_sharing_manager.dart @@ -2,9 +2,9 @@ import 'dart:convert'; import 'package:core/utils/app_logger.dart'; -import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/account/authentication_type.dart'; import 'package:model/account/personal_account.dart'; import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/login/data/local/authentication_info_cache_manager.dart'; @@ -50,20 +50,22 @@ class IOSSharingManager { } } - Future saveKeyChainSharingSession(PersonalAccount personalAccount) async { + Future saveKeyChainSharingSession(PersonalAccount personalAccount) async { + log('IOSSharingManager::saveKeyChainSharingSession: START'); try { if (!_validateToSaveKeychain(personalAccount)) { - logError('IOSSharingManager::saveKeyChainSharingSession: account is null'); + logError('IOSSharingManager::saveKeyChainSharingSession: AccountId | Username | ApiUrl is NULL'); return Future.value(null); } - Tuple2 authenticationInfo = await Future.wait( - [ - _getTokenOidc(tokeHashId: personalAccount.id), - _getCredentialAuthentication() - ], - eagerError: true - ).then((listValue) => Tuple2(listValue[0] as TokenOIDC?, listValue[1] as String?)); + TokenOIDC? tokenOIDC; + String? credentialInfo; + + if (personalAccount.authenticationType == AuthenticationType.oidc) { + tokenOIDC = await _getTokenOidc(tokeHashId: personalAccount.id); + } else { + credentialInfo = await _getCredentialAuthentication(); + } final emailDeliveryState = await _getEmailDeliveryState( accountId: personalAccount.accountId!, @@ -84,13 +86,15 @@ class IOSSharingManager { apiUrl: personalAccount.apiUrl!, emailState: emailState, emailDeliveryState: emailDeliveryState, - tokenOIDC: authenticationInfo.value1, - basicAuth: authenticationInfo.value2, + tokenOIDC: tokenOIDC, + basicAuth: credentialInfo, tokenEndpoint: tokenRecords?.tokenEndpoint, oidcScopes: tokenRecords?.scopes, ); - log('IOSSharingManager::_saveKeyChainSharingSession: $keychainSharingSession'); + await _keychainSharingManager.save(keychainSharingSession); + + log('IOSSharingManager::_saveKeyChainSharingSession: COMPLETED'); } catch (e) { logError('IOSSharingManager::_saveKeyChainSharingSession: Exception: $e'); } @@ -111,9 +115,7 @@ class IOSSharingManager { Future _getTokenOidc({required String tokeHashId}) async { try { - final tokenOidc = await _tokenOidcCacheManager.getTokenOidc(tokeHashId); - log('IOSSharingManager::_getTokenOidc:tokenOidc: $tokenOidc'); - return tokenOidc; + return await _tokenOidcCacheManager.getTokenOidc(tokeHashId); } catch (e) { logError('IOSSharingManager::_getTokenOidc:Exception: $e'); return null; @@ -123,7 +125,6 @@ class IOSSharingManager { Future _getCredentialAuthentication() async { try { final credentialInfo = await _authenticationInfoCacheManager.getAuthenticationInfoStored(); - log('IOSSharingManager::_getCredentialAuthentication:credentialInfo: $credentialInfo'); return base64Encode(utf8.encode('${credentialInfo.username}:${credentialInfo.password}')); } catch (e) { logError('IOSSharingManager::_getCredentialAuthentication:Exception: $e'); @@ -136,9 +137,7 @@ class IOSSharingManager { required UserName userName }) async { try { - final emailDeliveryState = await getEmailDeliveryStateFromKeychain(accountId); - log('IOSSharingManager::_getEmailState:emailDeliveryState: $emailDeliveryState'); - return emailDeliveryState; + return await getEmailDeliveryStateFromKeychain(accountId); } catch (e) { logError('IOSSharingManager::_getEmailDeliveryState:Exception: $e'); return null; @@ -167,7 +166,6 @@ class IOSSharingManager { try { final oidcConfig = await _oidcConfigurationCacheManager.getOidcConfiguration(); final oidcDiscoveryResponse = await _oidcHttpClient.discoverOIDC(oidcConfig); - log('IOSSharingManager::_getTokenEndpointAndScopes:oidcDiscoveryResponse = $oidcDiscoveryResponse | oidcConfig = $oidcConfig'); return ( tokenEndpoint: oidcDiscoveryResponse.tokenEndpoint, scopes: oidcConfig.scopes @@ -180,7 +178,6 @@ class IOSSharingManager { Future updateEmailStateInKeyChain(AccountId accountId, String newEmailState) async { final keychainSharingStored = await getKeychainSharingSession(accountId); - log('IOSSharingManager::updateEmailStateInKeyChain:keychainSharingStored: $keychainSharingStored | newEmailState: $newEmailState'); if (keychainSharingStored == null) { return; } diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart index 48126265c..f3661ca9b 100644 --- a/model/lib/extensions/session_extension.dart +++ b/model/lib/extensions/session_extension.dart @@ -67,6 +67,8 @@ extension SessionExtension on Session { throw NotFoundPersonalAccountException(); } + AccountId get accountId => personalAccount.accountId; + ({ bool isAvailable, CalendarEventCapability? calendarEventCapability diff --git a/model/lib/oidc/token_oidc.dart b/model/lib/oidc/token_oidc.dart index 05afa3448..50fd2798f 100644 --- a/model/lib/oidc/token_oidc.dart +++ b/model/lib/oidc/token_oidc.dart @@ -1,5 +1,4 @@ -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'; @@ -40,8 +39,6 @@ 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; diff --git a/test/features/home/presentation/home_controller_test.dart b/test/features/home/presentation/home_controller_test.dart index fff4bf85f..eea721ebf 100644 --- a/test/features/home/presentation/home_controller_test.dart +++ b/test/features/home/presentation/home_controller_test.dart @@ -23,7 +23,7 @@ import 'package:tmail_ui_user/features/login/domain/state/get_stored_token_oidc_ import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/update_authentication_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; @@ -49,7 +49,7 @@ import 'home_controller_test.mocks.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -66,7 +66,7 @@ void main() { late MockGetSessionInteractor mockGetSessionInteractor; late MockGetAuthenticatedAccountInteractor mockGetAuthenticatedAccountInteractor; - late MockUpdateAuthenticationAccountInteractor mockUpdateAuthenticationAccountInteractor; + late MockUpdateAccountCacheInteractor mockUpdateAccountCacheInteractor; late MockCachingManager mockCachingManager; late MockLanguageCacheManager mockLanguageCacheManager; @@ -91,7 +91,7 @@ void main() { // mock reloadable controller mockGetSessionInteractor = MockGetSessionInteractor(); mockGetAuthenticatedAccountInteractor = MockGetAuthenticatedAccountInteractor(); - mockUpdateAuthenticationAccountInteractor = MockUpdateAuthenticationAccountInteractor(); + mockUpdateAccountCacheInteractor = MockUpdateAccountCacheInteractor(); //mock base controller mockCachingManager = MockCachingManager(); @@ -109,7 +109,7 @@ void main() { Get.put(mockGetSessionInteractor); Get.put(mockGetAuthenticatedAccountInteractor); - Get.put(mockUpdateAuthenticationAccountInteractor); + Get.put(mockUpdateAccountCacheInteractor); Get.put(mockCachingManager); Get.put(mockLanguageCacheManager); diff --git a/test/features/login/presentation/login_controller_test.dart b/test/features/login/presentation/login_controller_test.dart index 0c9750e57..a29d5aecb 100644 --- a/test/features/login/presentation/login_controller_test.dart +++ b/test/features/login/presentation/login_controller_test.dart @@ -28,7 +28,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/update_authentication_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_controller.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; @@ -63,7 +63,7 @@ import 'login_controller_test.mocks.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -84,7 +84,7 @@ void main() { late MockDNSLookupToGetJmapUrlInteractor mockDNSLookupToGetJmapUrlInteractor; late MockGetSessionInteractor mockGetSessionInteractor; late MockGetAuthenticatedAccountInteractor mockGetAuthenticatedAccountInteractor; - late MockUpdateAuthenticationAccountInteractor mockUpdateAuthenticationAccountInteractor; + late MockUpdateAccountCacheInteractor mockUpdateAccountCacheInteractor; late CachingManager mockCachingManager; late LanguageCacheManager mockLanguageCacheManager; late MockAuthorizationInterceptors mockAuthorizationInterceptors; @@ -119,7 +119,7 @@ void main() { // mock reloadable controller mockGetSessionInteractor = MockGetSessionInteractor(); mockGetAuthenticatedAccountInteractor = MockGetAuthenticatedAccountInteractor(); - mockUpdateAuthenticationAccountInteractor = MockUpdateAuthenticationAccountInteractor(); + mockUpdateAccountCacheInteractor = MockUpdateAccountCacheInteractor(); //mock base controller mockCachingManager = MockCachingManager(); @@ -137,7 +137,7 @@ void main() { Get.put(mockGetSessionInteractor); Get.put(mockGetAuthenticatedAccountInteractor); - Get.put(mockUpdateAuthenticationAccountInteractor); + Get.put(mockUpdateAccountCacheInteractor); Get.put(mockCachingManager); Get.put(mockLanguageCacheManager); Get.put(mockAuthorizationInterceptors); diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index dd1cc34f9..a79febbd4 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -34,7 +34,7 @@ import 'package:tmail_ui_user/features/login/data/network/interceptors/authoriza import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/update_authentication_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_default_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart'; @@ -138,7 +138,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), - MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -234,7 +234,7 @@ void main() { // mock reloadable controller Get dependencies final getSessionInteractor = MockGetSessionInteractor(); final getAuthenticatedAccountInteractor = MockGetAuthenticatedAccountInteractor(); - final updateAuthenticationAccountInteractor = MockUpdateAuthenticationAccountInteractor(); + final updateAccountCacheInteractor = MockUpdateAccountCacheInteractor(); // mock mailbox controller direct dependencies final createNewMailboxInteractor = MockCreateNewMailboxInteractor(); @@ -299,7 +299,7 @@ void main() { Get.put(applicationManager); Get.put(getSessionInteractor); Get.put(getAuthenticatedAccountInteractor); - Get.put(updateAuthenticationAccountInteractor); + Get.put(updateAccountCacheInteractor); Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor);