diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index eacf5b8cb3..562b89cd74 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -23,8 +23,7 @@ import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/account/authentication_type.dart'; import 'package:rule_filter/rule_filter/capability_rule_filter.dart'; -import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart'; -import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart'; +import 'package:tmail_ui_user/features/base/consume_view_state_ui_controller.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/mdn_interactor_bindings.dart'; import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; @@ -61,9 +60,7 @@ import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; import 'package:uuid/uuid.dart'; -abstract class BaseController extends GetxController - with MessageDialogActionMixin, - PopupContextMenuActionMixin { +abstract class BaseController extends ConsumeViewStateUIController { final CachingManager cachingManager = Get.find(); final LanguageCacheManager languageCacheManager = Get.find(); @@ -84,21 +81,9 @@ abstract class BaseController extends GetxController GetStoredFirebaseRegistrationInteractor? _getStoredFirebaseRegistrationInteractor; DestroyFirebaseRegistrationInteractor? _destroyFirebaseRegistrationInteractor; - final viewState = Rx>(Right(UIState.idle)); FpsCallback? fpsCallback; - void consumeState(Stream> newStateStream) async { - newStateStream.listen(onData, onError: onError, onDone: onDone); - } - - void dispatchState(Either newState) { - viewState.value = newState; - } - - void clearState() { - viewState.value = Right(UIState.idle); - } - + @override void onData(Either newState) { viewState.value = newState; viewState.value.fold( @@ -118,6 +103,7 @@ abstract class BaseController extends GetxController handleSuccessViewState); } + @override void onError(Object error, StackTrace stackTrace) { logError('BaseController::onError():error: $error | stackTrace: $stackTrace'); final exception = _performFilterExceptionInError(error); @@ -128,8 +114,6 @@ abstract class BaseController extends GetxController } } - void onDone() {} - Exception? _performFilterExceptionInError(dynamic error) { logError('BaseController::_performFilterExceptionInError(): $error'); if (error is NoNetworkError || error is ConnectionTimeout || error is InternalServerError) { @@ -174,6 +158,7 @@ abstract class BaseController extends GetxController } } + @override void handleFailureViewState(Failure failure) async { logError('BaseController::handleFailureViewState(): ${failure.runtimeType}'); if (failure is LogoutOidcFailure) { @@ -188,6 +173,7 @@ abstract class BaseController extends GetxController } } + @override void handleSuccessViewState(Success success) async { log('BaseController::handleSuccessViewState(): ${success.runtimeType}'); if (success is LogoutOidcSuccess) { diff --git a/lib/features/base/consume_view_state_ui_controller.dart b/lib/features/base/consume_view_state_ui_controller.dart new file mode 100644 index 0000000000..ea4a54d64f --- /dev/null +++ b/lib/features/base/consume_view_state_ui_controller.dart @@ -0,0 +1,37 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart'; +import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart'; + +abstract class ConsumeViewStateUIController extends GetxController + with MessageDialogActionMixin, PopupContextMenuActionMixin { + + final viewState = Rx>(Right(UIState.idle)); + + void consumeState(Stream> newStateStream) { + newStateStream.listen(onData, onError: onError, onDone: onDone); + } + + void dispatchState(Either newState) { + viewState.value = newState; + } + + void clearState() { + viewState.value = Right(UIState.idle); + } + + void onData(Either newState) { + viewState.value = newState; + viewState.value.fold(handleFailureViewState, handleSuccessViewState); + } + + void onError(Object error, StackTrace stackTrace) {} + + void onDone() {} + + void handleFailureViewState(Failure failure); + + void handleSuccessViewState(Success success); +} \ No newline at end of file diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index 71ecafff7d..f3a63040a5 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -6,6 +6,7 @@ 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/account/personal_account.dart'; import 'package:model/extensions/session_extension.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; @@ -81,10 +82,10 @@ abstract class ReloadableController extends BaseController { void _handleGetCredentialSuccess(GetCredentialViewState credentialViewState) { _setUpInterceptors(credentialViewState); - getSessionAction(); + getSessionAction(personalAccount: credentialViewState.personalAccount); } - void getSessionAction() { + void getSessionAction({PersonalAccount? personalAccount}) { consumeState(_getSessionInteractor.execute()); } @@ -100,11 +101,15 @@ abstract class ReloadableController extends BaseController { void _handleGetSessionSuccess(GetSessionSuccess success) { final session = success.session; - final personalAccount = session.personalAccount; + final jmapPersonalAccount = session.jmapPersonalAccount; final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl); if (apiUrl.isNotEmpty) { dynamicUrlInterceptors.changeBaseUrl(apiUrl); - updateAuthenticationAccount(session, personalAccount.accountId, session.username); + updateAuthenticationAccount( + apiUrl: apiUrl, + accountId: jmapPersonalAccount.accountId, + userName: session.username + ); handleReloaded(session); } else { clearDataAndGoToLoginPage(); @@ -115,7 +120,7 @@ abstract class ReloadableController extends BaseController { void _handleGetStoredTokenOIDCSuccess(GetStoredTokenOidcSuccess tokenOidcSuccess) { _setUpInterceptorsOidc(tokenOidcSuccess); - getSessionAction(); + getSessionAction(personalAccount: tokenOidcSuccess.personalAccount); } void _setUpInterceptorsOidc(GetStoredTokenOidcSuccess tokenOidcSuccess) { @@ -138,10 +143,14 @@ abstract class ReloadableController extends BaseController { } } - 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 updateAuthenticationAccount({ + required String apiUrl, + required AccountId accountId, + required UserName userName + }) { + consumeState(_updateAuthenticationAccountInteractor.execute( + accountId, + apiUrl, + userName)); } } \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 1ae8c76274..3dea594535 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -733,7 +733,8 @@ class ComposerController extends BaseController with DragDropFileMixin { } if (!isEnableEmailSendButton.value) { - showConfirmDialogAction(context, + showConfirmDialogAction( + context, AppLocalizations.of(context).message_dialog_send_email_without_recipient, AppLocalizations.of(context).add_recipients, title: AppLocalizations.of(context).sending_failed, @@ -749,7 +750,8 @@ class ComposerController extends BaseController with DragDropFileMixin { .where((emailAddress) => !EmailUtils.isEmailAddressValid(emailAddress.emailAddress)) .toList(); if (listEmailAddressInvalid.isNotEmpty) { - showConfirmDialogAction(context, + showConfirmDialogAction( + context, AppLocalizations.of(context).message_dialog_send_email_with_email_address_invalid, AppLocalizations.of(context).fix_email_addresses, onConfirmAction: () { @@ -766,7 +768,8 @@ class ComposerController extends BaseController with DragDropFileMixin { } if (subjectEmail.value == null || subjectEmail.isEmpty == true) { - showConfirmDialogAction(context, + showConfirmDialogAction( + context, AppLocalizations.of(context).message_dialog_send_email_without_a_subject, AppLocalizations.of(context).send_anyway, onConfirmAction: () => _handleSendMessages(context), @@ -912,7 +915,6 @@ class ComposerController extends BaseController with DragDropFileMixin { }) async { await showConfirmDialogAction( context, - title: '', AppLocalizations.of(context).warningMessageWhenSendEmailFailure, AppLocalizations.of(context).edit, cancelTitle: AppLocalizations.of(context).closeAnyway, @@ -2126,7 +2128,6 @@ class ComposerController extends BaseController with DragDropFileMixin { }) async { await showConfirmDialogAction( context, - title: '', AppLocalizations.of(context).warningMessageWhenSaveEmailToDraftsFailure, AppLocalizations.of(context).edit, cancelTitle: AppLocalizations.of(context).closeAnyway, diff --git a/lib/features/home/domain/extensions/session_extensions.dart b/lib/features/home/domain/extensions/session_extensions.dart index 5afafc577d..744640dde7 100644 --- a/lib/features/home/domain/extensions/session_extensions.dart +++ b/lib/features/home/domain/extensions/session_extensions.dart @@ -51,8 +51,8 @@ extension SessionExtensions on Session { try { if (GetUtils.isEmail(username.value)) { return username.value.split('@').last; - } else if (GetUtils.isEmail(personalAccount.name.value)) { - return personalAccount.name.value.split('@').last; + } else if (GetUtils.isEmail(jmapPersonalAccount.name.value)) { + return jmapPersonalAccount.name.value.split('@').last; } else { return ''; } diff --git a/lib/features/home/domain/state/get_session_cache_state.dart b/lib/features/home/domain/state/get_session_cache_state.dart new file mode 100644 index 0000000000..5dc45ea708 --- /dev/null +++ b/lib/features/home/domain/state/get_session_cache_state.dart @@ -0,0 +1,31 @@ +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:model/account/personal_account.dart'; + +class GetSessionCacheLoading extends LoadingState {} + +class GetSessionCacheSuccess extends UIState { + final Session session; + final PersonalAccount personalAccount; + + GetSessionCacheSuccess({ + required this.session, + required this.personalAccount + }); + + @override + List get props => [session]; +} + +class GetSessionCacheFailure extends FeatureFailure { + final PersonalAccount personalAccount; + + GetSessionCacheFailure({ + required this.personalAccount, + dynamic exception + }) : super(exception: exception); + + @override + List get props => [personalAccount, ...super.props]; +} \ No newline at end of file diff --git a/lib/features/home/domain/usecases/get_session_cache_interactor.dart b/lib/features/home/domain/usecases/get_session_cache_interactor.dart new file mode 100644 index 0000000000..1b48ff4943 --- /dev/null +++ b/lib/features/home/domain/usecases/get_session_cache_interactor.dart @@ -0,0 +1,26 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:tmail_ui_user/features/home/domain/repository/session_repository.dart'; +import 'package:tmail_ui_user/features/home/domain/state/get_session_cache_state.dart'; + +class GetSessionCacheInteractor { + final SessionRepository _sessionRepository; + + GetSessionCacheInteractor(this._sessionRepository); + + Stream> execute(PersonalAccount personalAccount) async* { + try { + yield Right(GetSessionCacheLoading()); + final sessionCache = await _sessionRepository.getStoredSession(); + yield Right(GetSessionCacheSuccess( + session: sessionCache, + personalAccount: personalAccount)); + } catch (e) { + yield Left(GetSessionCacheFailure( + personalAccount: personalAccount, + exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/home/presentation/home_bindings.dart b/lib/features/home/presentation/home_bindings.dart index 2e75157d2e..e67d4e8b47 100644 --- a/lib/features/home/presentation/home_bindings.dart +++ b/lib/features/home/presentation/home_bindings.dart @@ -11,6 +11,8 @@ 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/home/domain/repository/session_repository.dart'; +import 'package:tmail_ui_user/features/home/domain/usecases/get_session_cache_interactor.dart'; import 'package:tmail_ui_user/features/home/presentation/home_controller.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart'; @@ -28,6 +30,7 @@ class HomeBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); } @@ -54,6 +57,7 @@ class HomeBindings extends BaseBindings { Get.lazyPut(() => CleanupRecentLoginUrlCacheInteractor(Get.find())); Get.lazyPut(() => CleanupRecentLoginUsernameCacheInteractor(Get.find())); Get.lazyPut(() => CheckOIDCIsAvailableInteractor(Get.find())); + Get.lazyPut(() => GetSessionCacheInteractor(Get.find())); } @override diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart index c7a40a7d93..4b8e6c218c 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -1,3 +1,6 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; @@ -21,7 +24,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/home/domain/state/get_session_cache_state.dart'; +import 'package:tmail_ui_user/features/home/domain/usecases/get_session_cache_interactor.dart'; +import 'package:tmail_ui_user/features/login/data/extensions/personal_account_extension.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/reopen_app_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'; @@ -34,6 +41,7 @@ class HomeController extends ReloadableController { final CleanupRecentSearchCacheInteractor _cleanupRecentSearchCacheInteractor; final CleanupRecentLoginUrlCacheInteractor _cleanupRecentLoginUrlCacheInteractor; final CleanupRecentLoginUsernameCacheInteractor _cleanupRecentLoginUsernameCacheInteractor; + final GetSessionCacheInteractor _getSessionCacheInteractor; HomeController( this._cleanupEmailCacheInteractor, @@ -41,6 +49,7 @@ class HomeController extends ReloadableController { this._cleanupRecentSearchCacheInteractor, this._cleanupRecentLoginUrlCacheInteractor, this._cleanupRecentLoginUsernameCacheInteractor, + this._getSessionCacheInteractor, ); PersonalAccount? currentAccount; @@ -90,7 +99,8 @@ class HomeController extends ReloadableController { static void downloadCallback(String id, DownloadTaskStatus status, int progress) {} - void _cleanupCache() async { + Future _cleanupCache() async { + log('HomeController::_cleanupCache:'); await HiveCacheConfig.instance.onUpgradeDatabase(cachingManager); await Future.wait([ @@ -98,7 +108,7 @@ class HomeController extends ReloadableController { _cleanupRecentSearchCacheInteractor.execute(RecentSearchCleanupRule()), _cleanupRecentLoginUrlCacheInteractor.execute(RecentLoginUrlCleanupRule()), _cleanupRecentLoginUsernameCacheInteractor.execute(RecentLoginUsernameCleanupRule()), - ]).then((value) => getAuthenticatedAccountAction()); + ]).then((_) => getAuthenticatedAccountAction()); } void _registerReceivingSharingIntent() { @@ -130,4 +140,55 @@ class HomeController extends ReloadableController { } } } + + void _goToMailboxDashboardWithSession({ + required PersonalAccount personalAccount, + required Session session + }) { + log('HomeController::_goToMailboxDashboardWithSession:'); + popAndPush( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: ReopenAppArguments( + personalAccount: personalAccount, + session: session)); + } + + void _goToMailboxDashboardWithoutSession({required PersonalAccount personalAccount}) { + log('HomeController::_goToMailboxDashboardWithoutSession:'); + popAndPush( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: ReopenAppArguments(personalAccount: personalAccount)); + } + + @override + void getSessionAction({PersonalAccount? personalAccount}) { + log('HomeController::getSessionAction:'); + if (PlatformInfo.isMobile && personalAccount?.existAccountIdAndUserName == true) { + consumeState(_getSessionCacheInteractor.execute(personalAccount!)); + } else { + super.getSessionAction(); + } + } + + @override + void handleSuccessViewState(Success success) { + log('HomeController::handleSuccessViewState: ${success.runtimeType}'); + if (success is GetSessionCacheSuccess) { + _goToMailboxDashboardWithSession( + personalAccount: success.personalAccount, + session: success.session); + } else { + super.handleSuccessViewState(success); + } + } + + @override + void handleFailureViewState(Failure failure) { + log('HomeController::handleFailureViewState: ${failure.runtimeType}'); + if (failure is GetSessionCacheFailure) { + _goToMailboxDashboardWithoutSession(personalAccount: failure.personalAccount); + } else { + super.handleFailureViewState(failure); + } + } } \ No newline at end of file diff --git a/lib/features/login/data/extensions/personal_account_extension.dart b/lib/features/login/data/extensions/personal_account_extension.dart index c36dcb93cc..d18092a122 100644 --- a/lib/features/login/data/extensions/personal_account_extension.dart +++ b/lib/features/login/data/extensions/personal_account_extension.dart @@ -11,4 +11,6 @@ extension PersonalAccountExtension on PersonalAccount { apiUrl: apiUrl, userName: userName?.value); } + + bool get existAccountIdAndUserName => accountId != null && userName != null; } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/state/synchronize_latest_session_state.dart b/lib/features/mailbox_dashboard/domain/state/synchronize_latest_session_state.dart new file mode 100644 index 0000000000..d2207d2cf7 --- /dev/null +++ b/lib/features/mailbox_dashboard/domain/state/synchronize_latest_session_state.dart @@ -0,0 +1,19 @@ +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 SynchronizingLatestSession extends LoadingState {} + +class SynchronizeLatestSessionSuccess extends UIState { + final Session session; + + SynchronizeLatestSessionSuccess(this.session); + + @override + List get props => [session]; +} + +class SynchronizeLatestSessionFailure extends FeatureFailure { + + SynchronizeLatestSessionFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/usecases/synchronize_latest_session_interactor.dart b/lib/features/mailbox_dashboard/domain/usecases/synchronize_latest_session_interactor.dart new file mode 100644 index 0000000000..ce42d8262c --- /dev/null +++ b/lib/features/mailbox_dashboard/domain/usecases/synchronize_latest_session_interactor.dart @@ -0,0 +1,22 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/home/domain/repository/session_repository.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/synchronize_latest_session_state.dart'; + +class SynchronizeLatestSessionInteractor { + final SessionRepository _sessionRepository; + + SynchronizeLatestSessionInteractor(this._sessionRepository); + + Stream> execute() async* { + try { + yield Right(SynchronizingLatestSession()); + final newSession = await _sessionRepository.getSession(); + await _sessionRepository.storeSession(newSession); + yield Right(SynchronizeLatestSessionSuccess(newSession)); + } catch (e) { + yield Left(SynchronizeLatestSessionFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 375143f3ec..7f2df0e601 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -72,12 +72,14 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_last_time_dismissed_spam_reported_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_spam_report_state_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/synchronize_latest_session_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/synchronize_session_controller.dart'; import 'package:tmail_ui_user/features/manage_account/domain/repository/identity_repository.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/identity_interactors_bindings.dart'; @@ -152,6 +154,8 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find())); + Get.put(SynchronizeSessionController(Get.find())); + Get.put(MailboxDashBoardController( Get.find(), Get.find(), @@ -343,6 +347,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find() )); + Get.lazyPut(() => SynchronizeLatestSessionInteractor(Get.find())); } @override 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 6cbc94eb79..c1e97b9d59 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -18,6 +18,7 @@ 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/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; @@ -70,6 +71,7 @@ import 'package:tmail_ui_user/features/email/presentation/extensions/composer_ar import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_arguments.dart'; +import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; import 'package:tmail_ui_user/features/home/domain/usecases/store_session_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; @@ -89,6 +91,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/synchronize_session_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/set_error_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/composer_overlay_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; @@ -96,6 +99,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/down 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/reopen_app_arguments.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'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart'; @@ -164,6 +168,7 @@ class MailboxDashBoardController extends ReloadableController { final AppGridDashboardController appGridDashboardController = Get.find(); final SpamReportController spamReportController = Get.find(); final NetworkConnectionController networkConnectionController = Get.find(); + final SynchronizeSessionController synchronizeSessionController = Get.find(); final MoveToMailboxInteractor _moveToMailboxInteractor; final DeleteEmailPermanentlyInteractor _deleteEmailPermanentlyInteractor; @@ -363,7 +368,11 @@ class MailboxDashBoardController extends ReloadableController { } else if (success is MarkAsEmailReadSuccess) { _markAsReadEmailSuccess(success); } else if (success is DeleteSendingEmailSuccess) { - getAllSendingEmails(); + if (accountId.value != null && sessionCurrent != null) { + getAllSendingEmails( + accountId: accountId.value!, + userName: sessionCurrent!.username); + } } else if (success is UnsubscribeEmailSuccess) { _handleUnsubscribeMailSuccess(success.newEmail); } else if (success is RestoreDeletedMessageSuccess) { @@ -467,13 +476,15 @@ class MailboxDashBoardController extends ReloadableController { void _handleArguments() { final arguments = Get.arguments; - log('MailboxDashBoardController::_getSessionCurrent(): arguments = $arguments'); + log('MailboxDashBoardController::_handleArguments(): arguments = $arguments'); if (arguments is Session) { _handleSession(arguments); } else if (arguments is MailtoArguments) { _handleMailtoURL(arguments); } else if (arguments is PreviewEmailArguments) { _handleOpenEmailAction(arguments); + } else if (arguments is ReopenAppArguments) { + _handleReopenAppAction(arguments); } else { dispatchRoute(DashboardRoutes.thread); reload(); @@ -504,11 +515,13 @@ class MailboxDashBoardController extends ReloadableController { void _handleSession(Session session) { log('MailboxDashBoardController::_handleSession:'); - updateAuthenticationAccount( - session, - session.personalAccount.accountId, - session.username - ); + final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl); + if (apiUrl.isNotEmpty) { + updateAuthenticationAccount( + apiUrl: apiUrl, + accountId: session.jmapPersonalAccount.accountId, + userName: session.username); + } _setUpComponentsFromSession(session); @@ -520,21 +533,18 @@ class MailboxDashBoardController extends ReloadableController { } void _setUpComponentsFromSession(Session session) { - final currentAccountId = session.personalAccount.accountId; + final currentAccountId = session.jmapPersonalAccount.accountId; sessionCurrent = session; accountId.value = currentAccountId; - injectAutoCompleteBindings(session, currentAccountId); - injectRuleFilterBindings(session, currentAccountId); - injectVacationBindings(session, currentAccountId); - injectFCMBindings(session, currentAccountId); + injectDataBindings(session: session, accountId: currentAccountId); - _getVacationResponse(); - spamReportController.getSpamReportStateAction(); - _getAllIdentities(); + fetchingData( + accountId: currentAccountId, + session: session, + userName: session.username); if (PlatformInfo.isMobile) { - getAllSendingEmails(); _storeSessionAction(session); } } @@ -552,9 +562,62 @@ class MailboxDashBoardController extends ReloadableController { _handleNotificationMessageFromEmailId(arguments.emailId); } - void _getVacationResponse() { - if (accountId.value != null && _getAllVacationInteractor != null) { - consumeState(_getAllVacationInteractor!.execute(accountId.value!)); + void _handleReopenAppAction(ReopenAppArguments arguments) { + log('MailboxDashBoardController::_handleReopenAppAction:ACCOUNT: ${arguments.personalAccount}'); + dispatchRoute(DashboardRoutes.thread); + + if (arguments.session != null) { + sessionCurrent = arguments.session; + accountId.value = arguments.personalAccount.accountId; + + injectDataBindings( + session: arguments.session!, + accountId: arguments.personalAccount.accountId!); + + fetchingData( + accountId: arguments.personalAccount.accountId!, + userName: arguments.personalAccount.userName!, + session: arguments.session!); + } else { + accountId.value = arguments.personalAccount.accountId!; + + fetchingData( + accountId: arguments.personalAccount.accountId!, + userName: arguments.personalAccount.userName!); + } + + synchronizeSessionController.synchronizeSession(); + + if (!_notificationManager.isNotificationClickedOnTerminate) { + _handleClickLocalNotificationOnTerminated(); + } + } + + void injectDataBindings({Session? session, AccountId? accountId}) { + injectAutoCompleteBindings(session, accountId); + injectRuleFilterBindings(session, accountId); + injectVacationBindings(session, accountId); + injectFCMBindings(session, accountId); + } + + void fetchingData({ + required AccountId accountId, + Session? session, + UserName? userName + }) { + if (session != null) { + _getAllIdentities(accountId: accountId, session: session); + } + if (PlatformInfo.isMobile && userName != null) { + getAllSendingEmails(accountId: accountId, userName: userName); + } + _getVacationResponse(accountId: accountId); + spamReportController.getSpamReportStateAction(); + } + + void _getVacationResponse({required AccountId accountId}) { + if (_getAllVacationInteractor != null) { + consumeState(_getAllVacationInteractor!.execute(accountId)); } } @@ -1480,13 +1543,19 @@ class MailboxDashBoardController extends ReloadableController { vacationResponse.value = result.value1; dispatchMailboxUIAction(RefreshChangeMailboxAction(null)); } - await Future.delayed( - const Duration(milliseconds: 500), - () => _replaceBrowserHistory(uri: result.value2) - ); + + if (PlatformInfo.isWeb) { + await Future.delayed( + const Duration(milliseconds: 500), + () => _replaceBrowserHistory(uri: result.value2)); + } } - _getAllIdentities(); + if (accountId.value != null && sessionCurrent != null) { + _getAllIdentities( + accountId: accountId.value!, + session: sessionCurrent!); + } } void selectQuickSearchFilter(QuickSearchFilter filter) { @@ -1560,13 +1629,19 @@ class MailboxDashBoardController extends ReloadableController { vacationResponse.value = result.value1; dispatchMailboxUIAction(RefreshChangeMailboxAction(null)); } - await Future.delayed( - const Duration(milliseconds: 500), - () => _replaceBrowserHistory(uri: result.value2) - ); + + if (PlatformInfo.isWeb) { + await Future.delayed( + const Duration(milliseconds: 500), + () => _replaceBrowserHistory(uri: result.value2)); + } } - _getAllIdentities(); + if (accountId.value != null && sessionCurrent != null) { + _getAllIdentities( + accountId: accountId.value!, + session: sessionCurrent!); + } } void _handleUpdateVacationSuccess(UpdateVacationSuccess success) { @@ -2030,7 +2105,11 @@ class MailboxDashBoardController extends ReloadableController { } void _handleStoreSendingEmailSuccess(StoreSendingEmailSuccess success) { - getAllSendingEmails(); + if (accountId.value != null && sessionCurrent != null) { + getAllSendingEmails( + accountId: accountId.value!, + userName: sessionCurrent!.username); + } if (currentOverlayContext != null && currentContext != null) { appToast.showToastWarningMessage( currentOverlayContext!, @@ -2041,7 +2120,11 @@ class MailboxDashBoardController extends ReloadableController { } void _handleUpdateSendingEmailSuccess(UpdateSendingEmailSuccess success) async { - getAllSendingEmails(); + if (accountId.value != null && sessionCurrent != null) { + getAllSendingEmails( + accountId: accountId.value!, + userName: sessionCurrent!.username); + } if (currentOverlayContext != null && currentContext != null) { appToast.showToastWarningMessage( currentOverlayContext!, @@ -2051,13 +2134,11 @@ class MailboxDashBoardController extends ReloadableController { } } - void getAllSendingEmails() { - if (accountId.value != null && sessionCurrent != null) { - consumeState(_getAllSendingEmailInteractor.execute( - accountId.value!, - sessionCurrent!.username - )); - } + void getAllSendingEmails({ + required AccountId accountId, + required UserName userName + }) { + consumeState(_getAllSendingEmailInteractor.execute(accountId, userName)); } void _handleGetAllSendingEmailsSuccess(GetAllSendingEmailSuccess success) async { @@ -2286,37 +2367,35 @@ class MailboxDashBoardController extends ReloadableController { void _replaceBrowserHistory({Uri? uri}) { log('MailboxDashBoardController::_replaceBrowserHistory:uri: $uri'); - if (PlatformInfo.isWeb) { - final selectedMailboxId = selectedMailbox.value?.id; - final selectedEmailId = selectedEmail.value?.id; - final isSearchRunning = searchController.isSearchEmailRunning; - String title = ''; - if (selectedEmail.value != null) { - title = 'Email-${selectedEmailId?.asString ?? ''}'; - } else if (isSearchRunning) { - title = 'SearchEmail'; - } else { - title = 'Mailbox-${selectedMailboxId?.asString}'; - } - RouteUtils.replaceBrowserHistory( - title: title, - url: uri ?? RouteUtils.createUrlWebLocationBar( - AppRoutes.dashboard, - router: NavigationRouter( - emailId: selectedEmail.value?.id, - mailboxId: isSearchRunning - ? null - : selectedMailboxId, - dashboardType: isSearchRunning - ? DashboardType.search - : DashboardType.normal, - searchQuery: isSearchRunning - ? searchController.searchQuery - : null - ) + final selectedMailboxId = selectedMailbox.value?.id; + final selectedEmailId = selectedEmail.value?.id; + final isSearchRunning = searchController.isSearchEmailRunning; + String title = ''; + if (selectedEmail.value != null) { + title = 'Email-${selectedEmailId?.asString ?? ''}'; + } else if (isSearchRunning) { + title = 'SearchEmail'; + } else { + title = 'Mailbox-${selectedMailboxId?.asString}'; + } + RouteUtils.replaceBrowserHistory( + title: title, + url: uri ?? RouteUtils.createUrlWebLocationBar( + AppRoutes.dashboard, + router: NavigationRouter( + emailId: selectedEmail.value?.id, + mailboxId: isSearchRunning + ? null + : selectedMailboxId, + dashboardType: isSearchRunning + ? DashboardType.search + : DashboardType.normal, + searchQuery: isSearchRunning + ? searchController.searchQuery + : null ) - ); - } + ) + ); } bool _navigateToScreen() { @@ -2392,7 +2471,18 @@ class MailboxDashBoardController extends ReloadableController { log('MailboxDashBoardController::_onBackButtonInterceptor:currentRoute: ${Get.currentRoute} | _isDialogViewOpen: $_isDialogViewOpen'); if (_isDialogViewOpen) { popBack(); - _replaceBrowserHistory(); + + if (PlatformInfo.isMobile) { + if (synchronizeSessionController.isShowingWarningDialogSessionExpired) { + synchronizeSessionController.resynchronizeSession(); + } else if (synchronizeSessionController.isShowingWarningDialogResynchronizeSessionFailure) { + synchronizeSessionController.exitAndReLoginAction(); + } + } + + if (PlatformInfo.isWeb) { + _replaceBrowserHistory(); + } return true; } @@ -2527,13 +2617,11 @@ class MailboxDashBoardController extends ReloadableController { PlatformInfo.isMobile; } - void _getAllIdentities() { - if (accountId.value != null && sessionCurrent != null) { - consumeState(_getAllIdentitiesInteractor.execute( - sessionCurrent!, - accountId.value! - )); - } + void _getAllIdentities({ + required AccountId accountId, + required Session session + }) { + consumeState(_getAllIdentitiesInteractor.execute(session, accountId)); } void _handleGetAllIdentitiesSuccess(GetAllIdentitiesSuccess success) async { diff --git a/lib/features/mailbox_dashboard/presentation/controller/synchronize_session_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/synchronize_session_controller.dart new file mode 100644 index 0000000000..84173e6cfd --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/controller/synchronize_session_controller.dart @@ -0,0 +1,187 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/extensions/session_extension.dart'; +import 'package:tmail_ui_user/features/base/consume_view_state_ui_controller.dart'; +import 'package:tmail_ui_user/features/base/state/base_ui_state.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/synchronize_latest_session_state.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/synchronize_latest_session_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class SynchronizeSessionController extends ConsumeViewStateUIController { + + final SynchronizeLatestSessionInteractor _synchronizeLatestSessionInteractor; + + final _imagePath = Get.find(); + + bool isResynchronized = false; + bool isShowingWarningDialogSessionExpired = false; + bool isShowingWarningDialogResynchronizeSessionFailure = false; + + SynchronizeSessionController(this._synchronizeLatestSessionInteractor); + + @override + void handleSuccessViewState(Success success) { + log('SynchronizeSessionController::handleSuccessViewState: ${success.runtimeType}'); + if (success is SynchronizeLatestSessionSuccess) { + _handleSynchronizeSessionSuccess(success); + } + } + + @override + void handleFailureViewState(Failure failure) { + logError('SynchronizeSessionController::handleFailureViewState: ${failure.runtimeType}'); + if (failure is SynchronizeLatestSessionFailure) { + _handleSynchronizeSessionFailure(failure); + } + } + + @override + void onClose() { + log('SynchronizeSessionController::onClose:'); + isShowingWarningDialogSessionExpired = false; + isShowingWarningDialogResynchronizeSessionFailure = false; + isResynchronized = false; + dispatchState(Right(UIClosedState())); + super.onClose(); + } + + void synchronizeSession() { + consumeState(_synchronizeLatestSessionInteractor.execute()); + } + + void resynchronizeSession() { + isShowingWarningDialogSessionExpired = false; + isResynchronized = true; + synchronizeSession(); + } + + void _handleSynchronizeSessionSuccess(SynchronizeLatestSessionSuccess success) { + final dashboardController = getBinding(); + if (dashboardController != null) { + final accountId = success.session.jmapPersonalAccount.accountId; + + dashboardController.sessionCurrent = success.session; + dashboardController.accountId.value = accountId; + + dashboardController.injectDataBindings( + session: success.session, + accountId: accountId); + + dashboardController.fetchingData( + accountId: accountId, + session: success.session, + userName: success.session.username); + } + } + + void _handleSynchronizeSessionFailure(SynchronizeLatestSessionFailure failure) { + if (currentContext == null) { + logError('SynchronizeSessionController::_handleSynchronizeSessionFailure: CONTEXT IS NULL'); + return; + } + + if (!isResynchronized) { + _showWarningDialogSessionExpired(currentContext!); + } else { + _showWarningDialogResynchronizeSessionFailure(currentContext!); + } + } + + Future _showWarningDialogSessionExpired(BuildContext context) async { + log('SynchronizeSessionController::_showWarningDialogSessionExpired:'); + isShowingWarningDialogSessionExpired = true; + + await showConfirmDialogAction( + context, + AppLocalizations.of(context).warningMessageWhenSynchronizeSessionFailure, + AppLocalizations.of(context).resynchronize, + cancelTitle: AppLocalizations.of(context).exitAndReLogin, + alignCenter: true, + outsideDismissible: false, + autoPerformPopBack: false, + isArrangeActionButtonsVertical: true, + onConfirmAction: () { + popBack(); + resynchronizeSession(); + }, + onCancelAction: () { + popBack(); + exitAndReLoginAction(); + }, + messageStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 14, + color: AppColor.colorTextBody + ), + actionStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 17, + color: Colors.white + ), + cancelStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 17, + color: Colors.black + ), + marginIcon: const EdgeInsets.only(top: 16), + icon: SvgPicture.asset( + _imagePath.icQuotasWarning, + width: 40, + height: 40, + colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), + ) + ); + } + + Future _showWarningDialogResynchronizeSessionFailure(BuildContext context) async { + log('SynchronizeSessionController::_showWarningDialogResynchronizeSessionFailure:'); + isShowingWarningDialogResynchronizeSessionFailure = true; + + await showConfirmDialogAction( + context, + AppLocalizations.of(context).warningMessageWhenResynchronizeSessionFailure, + AppLocalizations.of(context).exitAndReLogin, + alignCenter: true, + outsideDismissible: false, + autoPerformPopBack: false, + isArrangeActionButtonsVertical: true, + hasCancelButton: false, + onConfirmAction: () { + popBack(); + exitAndReLoginAction(); + }, + messageStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 14, + color: AppColor.colorTextBody + ), + actionStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 17, + color: Colors.white + ), + marginIcon: const EdgeInsets.only(top: 16), + icon: SvgPicture.asset( + _imagePath.icQuotasWarning, + width: 40, + height: 40, + colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), + ) + ); + } + + void exitAndReLoginAction() { + log('SynchronizeSessionController::exitAndReLoginAction:'); + isShowingWarningDialogSessionExpired = false; + isShowingWarningDialogResynchronizeSessionFailure = false; + isResynchronized = false; + final dashboardController = getBinding(); + if (dashboardController != null) { + dashboardController.clearDataAndGoToLoginPage(); + } + } +} diff --git a/lib/features/mailbox_dashboard/presentation/model/reopen_app_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/reopen_app_arguments.dart new file mode 100644 index 0000000000..a97dc56f19 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/model/reopen_app_arguments.dart @@ -0,0 +1,14 @@ +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:tmail_ui_user/main/routes/router_arguments.dart'; + +class ReopenAppArguments extends RouterArguments { + + final Session? session; + final PersonalAccount personalAccount; + + ReopenAppArguments({required this.personalAccount, this.session}); + + @override + List get props => [personalAccount, session]; +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/synchronizing_session_bar.dart b/lib/features/mailbox_dashboard/presentation/widgets/synchronizing_session_bar.dart new file mode 100644 index 0000000000..c2d35c3219 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/synchronizing_session_bar.dart @@ -0,0 +1,36 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/synchronize_latest_session_state.dart'; + +class SynchronizingSessionBar extends StatelessWidget { + + final Either viewState; + + const SynchronizingSessionBar({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is SynchronizingLatestSession) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 6), + margin: const EdgeInsetsDirectional.only(bottom: 8), + child: Center( + child: LinearProgressIndicator( + color: AppColor.primaryColor.withOpacity(0.5), + minHeight: 2, + backgroundColor: AppColor.primaryColor.withOpacity(0.3) + ), + ) + ); + } + return const SizedBox.shrink(); + }); + } +} 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 acff137cbe..162a2e6268 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.jmapPersonalAccount.accountId; _bindingInteractorForMenuItemView(sessionCurrent, accountId.value); _getVacationResponse(); _getParametersRouter(); @@ -95,7 +95,7 @@ class ManageAccountDashBoardController extends ReloadableController { final arguments = Get.arguments; if (arguments is ManageAccountArguments) { sessionCurrent = arguments.session; - accountId.value = arguments.session?.personalAccount.accountId; + accountId.value = arguments.session?.jmapPersonalAccount.accountId; previousUri = arguments.previousUri; _bindingInteractorForMenuItemView(sessionCurrent, accountId.value); _getVacationResponse(); 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 cc8064914c..60c9afaee5 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.jmapPersonalAccount.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 9c69a7153d..e590b97e7f 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.jmapPersonalAccount.accountId, userName: success.session.username, stateChange: stateChange, session: success.session); diff --git a/lib/features/sending_queue/presentation/sending_queue_controller.dart b/lib/features/sending_queue/presentation/sending_queue_controller.dart index 9606ce1319..eea5f1b615 100644 --- a/lib/features/sending_queue/presentation/sending_queue_controller.dart +++ b/lib/features/sending_queue/presentation/sending_queue_controller.dart @@ -158,7 +158,12 @@ class SendingQueueController extends BaseController with MessageDialogActionMixi bool get isAllUnSelected => dashboardController.listSendingEmails.isAllUnSelected(); void refreshSendingQueue() { - dashboardController.getAllSendingEmails(); + if (dashboardController.accountId.value != null + && dashboardController.sessionCurrent != null) { + dashboardController.getAllSendingEmails( + accountId: dashboardController.accountId.value!, + userName: dashboardController.sessionCurrent!.username); + } } void openMailboxMenu() { diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index a5861163e3..a06781432b 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -142,8 +142,8 @@ class ThreadController extends BaseController with EmailActionController { @override void onReady() { - dispatchState(Right(LoadingState())); super.onReady(); + consumeState(Stream.value(Right(GetAllEmailLoading()))); } @override @@ -422,6 +422,8 @@ class ThreadController extends BaseController with EmailActionController { ); } clearState(); + } else { + clearState(); } } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 3d03d3453e..c895901422 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -13,6 +13,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_c import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/synchronizing_session_bar.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_banner_widget.dart'; @@ -109,6 +110,10 @@ class ThreadView extends GetWidget return const SizedBox.shrink(); } }), + if (PlatformInfo.isMobile) + Obx(() => SynchronizingSessionBar( + viewState: controller.mailboxDashBoardController.synchronizeSessionController.viewState.value + )), SearchBarView( key: const Key('email_search_bar_view'), imagePaths: controller.imagePaths, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 3e90dab543..9a135981b2 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-03-19T12:10:23.549474", + "@@last_modified": "2024-06-25T11:38:27.513110", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3909,5 +3909,29 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "warningMessageWhenSynchronizeSessionFailure": "The current session has expired.\nPlease resynchronize or log in again with a new session.", + "@warningMessageWhenSynchronizeSessionFailure": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "resynchronize": "Resynchronize", + "@resynchronize": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "exitAndReLogin": "Exit & Re-login", + "@exitAndReLogin": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "warningMessageWhenResynchronizeSessionFailure": "Resynchronize session failure. You must log in again to continue using!", + "@warningMessageWhenResynchronizeSessionFailure": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 2f1733d719..758267eb62 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4085,4 +4085,28 @@ class AppLocalizations { name: 'showNotifications', ); } + + String get warningMessageWhenSynchronizeSessionFailure { + return Intl.message( + 'The current session has expired.\nPlease resynchronize or log in again with a new session.', + name: 'warningMessageWhenSynchronizeSessionFailure'); + } + + String get resynchronize { + return Intl.message( + 'Resynchronize', + name: 'resynchronize'); + } + + String get exitAndReLogin { + return Intl.message( + 'Exit & Re-login', + name: 'exitAndReLogin'); + } + + String get warningMessageWhenResynchronizeSessionFailure { + return Intl.message( + 'Resynchronize session failure. You must log in again to continue using!', + name: 'warningMessageWhenResynchronizeSessionFailure'); + } } \ No newline at end of file diff --git a/model/lib/error_type_handler/account_exception.dart b/model/lib/error_type_handler/account_exception.dart index a2c6a25676..9bcdd71d41 100644 --- a/model/lib/error_type_handler/account_exception.dart +++ b/model/lib/error_type_handler/account_exception.dart @@ -1,2 +1,2 @@ -class NotFoundPersonalAccountException implements Exception {} \ No newline at end of file +class NotFoundJMAPPersonalAccountException implements Exception {} \ No newline at end of file diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart index 48126265c0..c0966270ab 100644 --- a/model/lib/extensions/session_extension.dart +++ b/model/lib/extensions/session_extension.dart @@ -53,18 +53,18 @@ extension SessionExtension on Session { } } - JmapAccount get personalAccount { + JmapAccount get jmapPersonalAccount { if (accounts.isNotEmpty) { - final listPersonalAccount = accounts.entries + final listJMAPPersonalAccount = accounts.entries .map((entry) => entry.value.toJmapAccount(entry.key)) .where((jmapAccount) => jmapAccount.isPersonal) .toList(); - if (listPersonalAccount.isNotEmpty) { - return listPersonalAccount.first; + if (listJMAPPersonalAccount.isNotEmpty) { + return listJMAPPersonalAccount.first; } } - throw NotFoundPersonalAccountException(); + throw NotFoundJMAPPersonalAccountException(); } ({ diff --git a/test/features/home/presentation/home_controller_test.dart b/test/features/home/presentation/home_controller_test.dart index fff4bf85f7..22a6ee8560 100644 --- a/test/features/home/presentation/home_controller_test.dart +++ b/test/features/home/presentation/home_controller_test.dart @@ -16,6 +16,7 @@ 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/home/domain/usecases/get_session_cache_interactor.dart'; import 'package:tmail_ui_user/features/home/domain/usecases/get_session_interactor.dart'; import 'package:tmail_ui_user/features/home/presentation/home_controller.dart'; import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; @@ -46,6 +47,7 @@ import 'home_controller_test.mocks.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -63,6 +65,7 @@ void main() { late MockCleanupRecentSearchCacheInteractor cleanupRecentSearchCacheInteractor; late MockCleanupRecentLoginUrlCacheInteractor cleanupRecentLoginUrlCacheInteractor; late MockCleanupRecentLoginUsernameCacheInteractor cleanupRecentLoginUsernameCacheInteractor; + late MockGetSessionCacheInteractor getSessionCacheInteractor; late MockGetSessionInteractor mockGetSessionInteractor; late MockGetAuthenticatedAccountInteractor mockGetAuthenticatedAccountInteractor; @@ -87,6 +90,7 @@ void main() { cleanupRecentSearchCacheInteractor = MockCleanupRecentSearchCacheInteractor(); cleanupRecentLoginUrlCacheInteractor = MockCleanupRecentLoginUrlCacheInteractor(); cleanupRecentLoginUsernameCacheInteractor = MockCleanupRecentLoginUsernameCacheInteractor(); + getSessionCacheInteractor = MockGetSessionCacheInteractor(); // mock reloadable controller mockGetSessionInteractor = MockGetSessionInteractor(); @@ -134,7 +138,8 @@ void main() { emailReceiveManager, cleanupRecentSearchCacheInteractor, cleanupRecentLoginUrlCacheInteractor, - cleanupRecentLoginUsernameCacheInteractor + cleanupRecentLoginUsernameCacheInteractor, + getSessionCacheInteractor ); }); diff --git a/test/features/mailbox/repository/mailbox_respository_test.dart b/test/features/mailbox/repository/mailbox_respository_test.dart index 0407a7748c..705aa0aa25 100644 --- a/test/features/mailbox/repository/mailbox_respository_test.dart +++ b/test/features/mailbox/repository/mailbox_respository_test.dart @@ -30,7 +30,7 @@ void main() { late MailboxRepository mailboxRepository; final sessionFixture = SessionFixtures.aliceSession; - final accountIdFixture = SessionFixtures.aliceSession.personalAccount.accountId; + final accountIdFixture = SessionFixtures.aliceSession.jmapPersonalAccount.accountId; final userNameFixture = SessionFixtures.aliceSession.username; group('[getAllMailbox] method test', () { 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 dd1cc34f96..cc4a37b7e1 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 @@ -60,6 +60,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/synchronize_session_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.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'; @@ -133,6 +134,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(fallbackGenerators: fallbackGenerators), + MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(), MockSpec(), MockSpec(), @@ -210,6 +212,7 @@ void main() { final appGridDashboardController = MockAppGridDashboardController(); final spamReportController = MockSpamReportController(); final networkConnectionController = MockNetworkConnectionController(); + final synchronizeSessionController = MockSynchronizeSessionController(); // mock search controller direct dependencies final quickSearchEmailInteractor = MockQuickSearchEmailInteractor(); @@ -281,6 +284,7 @@ void main() { Get.put(appGridDashboardController); Get.put(spamReportController); Get.put(networkConnectionController); + Get.put(synchronizeSessionController); Get.put(cachingManager); Get.put(languageCacheManager); Get.put(authorizationInterceptors);