From fde095523f59754a49343bc232f77128218e90b4 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 10 May 2024 14:13:59 +0700 Subject: [PATCH] Fix missing notification when execute `receive new email` & `mark as read` at same time in terminated app --- .../home/domain/state/get_session_state.dart | 6 +- .../usecases/get_session_interactor.dart | 14 +- .../domain/state/get_credential_state.dart | 18 ++- .../state/get_stored_token_oidc_state.dart | 19 ++- .../get_authenticated_account_interactor.dart | 11 +- .../usecases/get_credential_interactor.dart | 11 +- .../get_stored_token_oidc_interactor.dart | 17 ++- .../controller/fcm_message_controller.dart | 130 ++++++++++-------- 8 files changed, 145 insertions(+), 81 deletions(-) diff --git a/lib/features/home/domain/state/get_session_state.dart b/lib/features/home/domain/state/get_session_state.dart index bd92476652..fb5928e6d1 100644 --- a/lib/features/home/domain/state/get_session_state.dart +++ b/lib/features/home/domain/state/get_session_state.dart @@ -1,16 +1,18 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; class GetSessionLoading extends LoadingState {} class GetSessionSuccess extends UIState { final Session session; + final StateChange? stateChange; - GetSessionSuccess(this.session); + GetSessionSuccess(this.session, {this.stateChange}); @override - List get props => [session]; + List get props => [session, stateChange]; } class GetSessionFailure extends FeatureFailure { diff --git a/lib/features/home/domain/usecases/get_session_interactor.dart b/lib/features/home/domain/usecases/get_session_interactor.dart index e81f774bf8..fd369079b6 100644 --- a/lib/features/home/domain/usecases/get_session_interactor.dart +++ b/lib/features/home/domain/usecases/get_session_interactor.dart @@ -3,6 +3,7 @@ import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; import 'package:tmail_ui_user/features/home/domain/repository/session_repository.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; @@ -11,26 +12,29 @@ class GetSessionInteractor { GetSessionInteractor(this.sessionRepository); - Stream> execute() async* { + Stream> execute({StateChange? stateChange}) async* { try { yield Right(GetSessionLoading()); final session = await sessionRepository.getSession(); - yield Right(GetSessionSuccess(session)); + yield Right(GetSessionSuccess(session, stateChange: stateChange)); } catch (e) { if (PlatformInfo.isMobile) { - yield* _getStoredSessionFromCache(remoteException: e); + yield* _getStoredSessionFromCache(remoteException: e, stateChange: stateChange); } else { yield Left(GetSessionFailure(e)); } } } - Stream> _getStoredSessionFromCache({dynamic remoteException}) async* { + Stream> _getStoredSessionFromCache({ + dynamic remoteException, + StateChange? stateChange + }) async* { try { log('GetSessionInteractor::_getStoredSessionFromCache:remoteException: $remoteException'); yield Right(GetSessionLoading()); final session = await sessionRepository.getStoredSession(); - yield Right(GetSessionSuccess(session)); + yield Right(GetSessionSuccess(session, stateChange: stateChange)); } catch (e) { yield Left(GetSessionFailure(remoteException ?? e)); } diff --git a/lib/features/login/domain/state/get_credential_state.dart b/lib/features/login/domain/state/get_credential_state.dart index 49c4ab7e2d..fe2523833b 100644 --- a/lib/features/login/domain/state/get_credential_state.dart +++ b/lib/features/login/domain/state/get_credential_state.dart @@ -1,17 +1,31 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; import 'package:model/account/password.dart'; +import 'package:model/account/personal_account.dart'; class GetCredentialViewState extends UIState { final Uri baseUrl; final UserName userName; final Password password; + final PersonalAccount personalAccount; + final StateChange? stateChange; - GetCredentialViewState(this.baseUrl, this.userName, this.password); + GetCredentialViewState( + this.baseUrl, + this.userName, + this.password, + this.personalAccount, + {this.stateChange}); @override - List get props => [baseUrl, userName, password]; + List get props => [ + baseUrl, + userName, + password, + personalAccount, + stateChange]; } class GetCredentialFailure extends FeatureFailure { diff --git a/lib/features/login/domain/state/get_stored_token_oidc_state.dart b/lib/features/login/domain/state/get_stored_token_oidc_state.dart index a8d30356dc..18236452b5 100644 --- a/lib/features/login/domain/state/get_stored_token_oidc_state.dart +++ b/lib/features/login/domain/state/get_stored_token_oidc_state.dart @@ -1,5 +1,7 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; +import 'package:model/account/personal_account.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; @@ -7,11 +9,24 @@ class GetStoredTokenOidcSuccess extends UIState { final Uri baseUrl; final TokenOIDC tokenOidc; final OIDCConfiguration oidcConfiguration; + final PersonalAccount personalAccount; + final StateChange? stateChange; - GetStoredTokenOidcSuccess(this.baseUrl, this.tokenOidc, this.oidcConfiguration); + GetStoredTokenOidcSuccess( + this.baseUrl, + this.tokenOidc, + this.oidcConfiguration, + this.personalAccount, + {this.stateChange} + ); @override - List get props => [baseUrl, tokenOidc, oidcConfiguration]; + List get props => [ + baseUrl, + tokenOidc, + oidcConfiguration, + personalAccount, + stateChange]; } class GetStoredTokenOidcFailure extends FeatureFailure { diff --git a/lib/features/login/domain/usecases/get_authenticated_account_interactor.dart b/lib/features/login/domain/usecases/get_authenticated_account_interactor.dart index 71eb479cde..0b2e3285ca 100644 --- a/lib/features/login/domain/usecases/get_authenticated_account_interactor.dart +++ b/lib/features/login/domain/usecases/get_authenticated_account_interactor.dart @@ -1,6 +1,7 @@ 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/push/state_change.dart'; import 'package:model/account/authentication_type.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_authenticated_account_state.dart'; @@ -18,15 +19,19 @@ class GetAuthenticatedAccountInteractor { this._getStoredTokenOidcInteractor ); - Stream> execute() async* { + Stream> execute({StateChange? stateChange}) async* { try { yield Right(LoadingState()); final account = await _accountRepository.getCurrentAccount(); yield Right(GetAuthenticatedAccountSuccess(account)); if (account.authenticationType == AuthenticationType.oidc) { - yield* _getStoredTokenOidcInteractor.execute(account.id); + yield* _getStoredTokenOidcInteractor.execute( + personalAccount: account, + stateChange: stateChange); } else { - yield await _getCredentialInteractor.execute(); + yield await _getCredentialInteractor.execute( + personalAccount: account, + stateChange: stateChange); } } catch (e) { yield Left(GetAuthenticatedAccountFailure(e)); diff --git a/lib/features/login/domain/usecases/get_credential_interactor.dart b/lib/features/login/domain/usecases/get_credential_interactor.dart index 621c260497..5b71548054 100644 --- a/lib/features/login/domain/usecases/get_credential_interactor.dart +++ b/lib/features/login/domain/usecases/get_credential_interactor.dart @@ -4,7 +4,9 @@ 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/user_name.dart'; +import 'package:jmap_dart_client/jmap/push/state_change.dart'; import 'package:model/account/password.dart'; +import 'package:model/account/personal_account.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; import 'package:tmail_ui_user/features/login/domain/extensions/uri_extension.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; @@ -15,7 +17,10 @@ class GetCredentialInteractor { GetCredentialInteractor(this.credentialRepository); - Future> execute() async { + Future> execute({ + required PersonalAccount personalAccount, + StateChange? stateChange + }) async { try { final baseUrl = await credentialRepository.getBaseUrl(); final authenticationInfo = await credentialRepository.getAuthenticationInfoStored(); @@ -23,7 +28,9 @@ class GetCredentialInteractor { return Right(GetCredentialViewState( baseUrl, UserName(authenticationInfo.username), - Password(authenticationInfo.password))); + Password(authenticationInfo.password), + personalAccount, + stateChange: stateChange)); } else { return Left(GetCredentialFailure(BadCredentials())); } diff --git a/lib/features/login/domain/usecases/get_stored_token_oidc_interactor.dart b/lib/features/login/domain/usecases/get_stored_token_oidc_interactor.dart index f64361282a..d45e0a0d91 100644 --- a/lib/features/login/domain/usecases/get_stored_token_oidc_interactor.dart +++ b/lib/features/login/domain/usecases/get_stored_token_oidc_interactor.dart @@ -2,6 +2,8 @@ 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/push/state_change.dart'; +import 'package:model/account/personal_account.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; @@ -16,13 +18,15 @@ class GetStoredTokenOidcInteractor { GetStoredTokenOidcInteractor(this._authenticationOIDCRepository, this._credentialRepository); - Stream> execute(String tokenIdHash) async* { + Stream> execute({ + required PersonalAccount personalAccount, + StateChange? stateChange + }) async* { try { - log('GetStoredTokenOidcInteractor::execute(): tokenIdHash: $tokenIdHash'); yield Right(LoadingState()); final futureValue = await Future.wait([ _credentialRepository.getBaseUrl(), - _authenticationOIDCRepository.getStoredTokenOIDC(tokenIdHash), + _authenticationOIDCRepository.getStoredTokenOIDC(personalAccount.id), _authenticationOIDCRepository.getStoredOidcConfiguration(), ], eagerError: true); @@ -33,7 +37,12 @@ class GetStoredTokenOidcInteractor { log('GetStoredTokenOidcInteractor::execute(): oidcConfiguration: $oidcConfiguration'); if (_isCredentialValid(baseUrl)) { - yield Right(GetStoredTokenOidcSuccess(baseUrl, tokenOidc, oidcConfiguration)); + yield Right(GetStoredTokenOidcSuccess( + baseUrl, + tokenOidc, + oidcConfiguration, + personalAccount, + stateChange: stateChange)); } else { yield Left(GetStoredTokenOidcFailure(InvalidBaseUrl())); } 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 5e51e88e7e..9c69a7153d 100644 --- a/lib/features/push_notification/presentation/controller/fcm_message_controller.dart +++ b/lib/features/push_notification/presentation/controller/fcm_message_controller.dart @@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/push/state_change.dart'; +import 'package:model/model.dart'; import 'package:rxdart/rxdart.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; @@ -21,7 +22,6 @@ 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/home/presentation/home_bindings.dart'; import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.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/usecases/get_authenticated_account_interactor.dart'; @@ -43,7 +43,6 @@ class FcmMessageController extends FcmBaseController { AccountId? _currentAccountId; Session? _currentSession; UserName? _userName; - Map? _payloadData; GetAuthenticatedAccountInteractor? _getAuthenticatedAccountInteractor; DynamicUrlInterceptors? _dynamicUrlInterceptors; @@ -90,21 +89,21 @@ class FcmMessageController extends FcmBaseController { void _handleForegroundMessageAction(Map payloadData) { log('FcmMessageController::_handleForegroundMessageAction():payloadData: $payloadData | _currentAccountId: $_currentAccountId'); if (_currentAccountId != null && _userName != null) { - final stateChange = _parsingPayloadData(payloadData); + final stateChange = FcmUtils.instance.convertFirebaseDataMessageToStateChange(payloadData); final mapTypeState = stateChange.getMapTypeState(_currentAccountId!); - _mappingTypeStateToAction(mapTypeState, _currentAccountId!, _userName!, session: _currentSession); + _mappingTypeStateToAction( + mapTypeState, + _currentAccountId!, + _userName!, + session: _currentSession); } } void _handleBackgroundMessageAction(Map payloadData) async { log('FcmMessageController::_handleBackgroundMessageAction():payloadData: $payloadData'); - _payloadData = payloadData; + final stateChange = FcmUtils.instance.convertFirebaseDataMessageToStateChange(payloadData); await _initialAppConfig(); - _getAuthenticatedAccount(); - } - - StateChange? _parsingPayloadData(Map payloadData) { - return FcmUtils.instance.convertFirebaseDataMessageToStateChange(payloadData); + _getAuthenticatedAccount(stateChange: stateChange); } void _mappingTypeStateToAction( @@ -199,26 +198,13 @@ class FcmMessageController extends FcmBaseController { FcmTokenController.instance.initialBindingInteractor(); } - void _getAuthenticatedAccount() { + void _getAuthenticatedAccount({StateChange? stateChange}) { if (_getAuthenticatedAccountInteractor != null) { - consumeState(_getAuthenticatedAccountInteractor!.execute()); - } else { - _clearPayloadData(); - logError('FcmMessageController::_getAuthenticatedAccount():_getAuthenticatedAccountInteractor is null'); - } - } - - void _handleGetAuthenticatedAccountSuccess(GetAuthenticatedAccountSuccess success) { - _currentAccountId = success.account.accountId; - _userName = success.account.userName; - if (!PlatformInfo.isAndroid) { - _dynamicUrlInterceptors?.changeBaseUrl(success.account.apiUrl); + consumeState(_getAuthenticatedAccountInteractor!.execute(stateChange: stateChange)); } - log('FcmMessageController::_handleGetAuthenticatedAccountSuccess():_currentAccountId: $_currentAccountId | _userName: $_userName'); } void _handleGetAccountByOidcSuccess(GetStoredTokenOidcSuccess storedTokenOidcSuccess) { - log('FcmMessageController::_handleGetAccountByOidcSuccess():'); _dynamicUrlInterceptors?.setJmapUrl(storedTokenOidcSuccess.baseUrl.toString()); _authorizationInterceptors?.setTokenAndAuthorityOidc( newToken: storedTokenOidcSuccess.tokenOidc, @@ -227,14 +213,24 @@ class FcmMessageController extends FcmBaseController { if (PlatformInfo.isAndroid) { _dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.baseUrl.toString()); - _getSessionAction(); + _getSessionAction(stateChange: storedTokenOidcSuccess.stateChange); } else { - _pushActionFromRemoteMessageBackground(); + _dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.personalAccount.apiUrl); + + final accountId = storedTokenOidcSuccess.personalAccount.accountId; + final username = storedTokenOidcSuccess.personalAccount.userName; + final stateChange = storedTokenOidcSuccess.stateChange; + + if (accountId != null && username != null && stateChange != null) { + _pushActionFromRemoteMessageBackground( + accountId: accountId, + userName: username, + stateChange: stateChange); + } } } void _handleGetAccountByBasicAuthSuccess(GetCredentialViewState credentialViewState) { - log('FcmMessageController::_handleGetAccountByBasicAuthSuccess():'); _dynamicUrlInterceptors?.setJmapUrl(credentialViewState.baseUrl.toString()); _authorizationInterceptors?.setBasicAuthorization( credentialViewState.userName, @@ -242,61 +238,73 @@ class FcmMessageController extends FcmBaseController { ); if (PlatformInfo.isAndroid) { _dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.baseUrl.toString()); - _getSessionAction(); + _getSessionAction(stateChange: credentialViewState.stateChange); } else { - _pushActionFromRemoteMessageBackground(); + _dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.personalAccount.apiUrl); + + final accountId = credentialViewState.personalAccount.accountId; + final username = credentialViewState.personalAccount.userName; + final stateChange = credentialViewState.stateChange; + + if (accountId != null && username != null && stateChange != null) { + _pushActionFromRemoteMessageBackground( + accountId: accountId, + userName: username, + stateChange: stateChange); + } } } - void _getSessionAction() { + void _getSessionAction({StateChange? stateChange}) { if (_getSessionInteractor != null) { - consumeState(_getSessionInteractor!.execute()); - } else { - _clearPayloadData(); - logError('FcmMessageController::_getSessionAction():_getSessionInteractor is null'); + consumeState(_getSessionInteractor!.execute(stateChange: stateChange)); } } void _handleGetSessionSuccess(GetSessionSuccess success) { - _currentSession = success.session; - _userName = success.session.username; - final apiUrl = success.session.getQualifiedApiUrl(baseUrl: _dynamicUrlInterceptors?.jmapUrl); - log('FcmMessageController::_pushActionFromRemoteMessageBackground():apiUrl: $apiUrl'); - if (apiUrl.isNotEmpty) { - _dynamicUrlInterceptors?.changeBaseUrl(apiUrl); - _pushActionFromRemoteMessageBackground(); - } else { - _clearPayloadData(); - logError('FcmMessageController::_handleGetSessionSuccess():apiUrl is null'); - } - } - - void _pushActionFromRemoteMessageBackground() { - log('FcmMessageController::_pushActionFromRemoteMessageBackground():_payloadData: $_payloadData | _currentAccountId: $_currentAccountId | _currentSession: $_currentSession'); - if (_payloadData?.isNotEmpty == true && _currentAccountId != null && _userName != null) { - final stateChange = _parsingPayloadData(_payloadData!); - final mapTypeState = stateChange.getMapTypeState(_currentAccountId!); - _mappingTypeStateToAction(mapTypeState, _currentAccountId!, _userName!, isForeground: false, session: _currentSession); + try { + final apiUrl = success.session.getQualifiedApiUrl(baseUrl: _dynamicUrlInterceptors?.jmapUrl); + final stateChange = success.stateChange; + + if (apiUrl.isNotEmpty && stateChange != null) { + _dynamicUrlInterceptors?.changeBaseUrl(apiUrl); + + _pushActionFromRemoteMessageBackground( + accountId: success.session.personalAccount.accountId, + userName: success.session.username, + stateChange: stateChange, + session: success.session); + } + } catch (e) { + logError('FcmMessageController::_handleGetSessionSuccess: Exception $e'); } - _clearPayloadData(); } - void _clearPayloadData() { - _payloadData = null; + void _pushActionFromRemoteMessageBackground({ + required AccountId accountId, + required UserName userName, + required StateChange stateChange, + Session? session + }) { + final mapTypeState = stateChange.getMapTypeState(accountId); + + _mappingTypeStateToAction( + mapTypeState, + accountId, + userName, + isForeground: false, + session: session); } @override void handleFailureViewState(Failure failure) { log('FcmMessageController::_handleFailureViewState(): $failure'); - _clearPayloadData(); } @override void handleSuccessViewState(Success success) { log('FcmMessageController::_handleSuccessViewState(): $success'); - if (success is GetAuthenticatedAccountSuccess) { - _handleGetAuthenticatedAccountSuccess(success); - } else if (success is GetSessionSuccess) { + if (success is GetSessionSuccess) { _handleGetSessionSuccess(success); } else if (success is GetStoredTokenOidcSuccess) { _handleGetAccountByOidcSuccess(success);