From 69a675dd8432d7c7caa4f1768ec96ad47d432c47 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 5 Jan 2024 19:33:22 +0700 Subject: [PATCH 01/17] TF-2431 Add get all account interactor Signed-off-by: dab246 --- .../data/datasource/account_datasource.dart | 2 ++ .../hive_account_datasource_impl.dart | 7 +++++++ .../data/local/account_cache_manager.dart | 12 ++++++++++- .../repository/account_repository_impl.dart | 5 +++++ .../exceptions/authentication_exception.dart | 2 ++ .../domain/repository/account_repository.dart | 2 ++ .../get_all_authenticated_account_state.dart | 17 ++++++++++++++++ ..._all_authenticated_account_interactor.dart | 20 +++++++++++++++++++ 8 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 lib/features/login/domain/state/get_all_authenticated_account_state.dart create mode 100644 lib/features/login/domain/usecases/get_all_authenticated_account_interactor.dart diff --git a/lib/features/login/data/datasource/account_datasource.dart b/lib/features/login/data/datasource/account_datasource.dart index 1f145075bd..3a1870d4cc 100644 --- a/lib/features/login/data/datasource/account_datasource.dart +++ b/lib/features/login/data/datasource/account_datasource.dart @@ -6,4 +6,6 @@ abstract class AccountDatasource { Future setCurrentAccount(PersonalAccount newCurrentAccount); Future deleteCurrentAccount(String accountId); + + Future> getAllAccount(); } \ No newline at end of file diff --git a/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart b/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart index d6c1606976..162f0ebd00 100644 --- a/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart +++ b/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart @@ -41,4 +41,11 @@ class HiveAccountDatasourceImpl extends AccountDatasource { return await _accountCacheManager.deleteCurrentAccount(accountId); }).catchError(_exceptionThrower.throwException); } + + @override + Future> getAllAccount() { + return Future.sync(() async { + return await _accountCacheManager.getAllAccount(); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/login/data/local/account_cache_manager.dart b/lib/features/login/data/local/account_cache_manager.dart index 3d3d6a43e4..69b37baa48 100644 --- a/lib/features/login/data/local/account_cache_manager.dart +++ b/lib/features/login/data/local/account_cache_manager.dart @@ -20,7 +20,7 @@ class AccountCacheManager { if (accountCache != null) { return accountCache.toAccount(); } else { - throw NotFoundAuthenticatedAccountException(); + throw NotFoundActiveAccountException(); } } @@ -47,4 +47,14 @@ class AccountCacheManager { log('AccountCacheManager::deleteCurrentAccount(): $hashId'); return _accountCacheClient.deleteItem(hashId); } + + Future> getAllAccount() async { + final allAccounts = await _accountCacheClient.getAll(); + log('AccountCacheManager::getAllAccount::allAccounts(): $allAccounts'); + if (allAccounts.isNotEmpty) { + return allAccounts.map((account) => account.toAccount()).toList(); + } else { + throw NotFoundAuthenticatedAccountException(); + } + } } \ No newline at end of file diff --git a/lib/features/login/data/repository/account_repository_impl.dart b/lib/features/login/data/repository/account_repository_impl.dart index 0df1f5dedc..4b6aceb8b1 100644 --- a/lib/features/login/data/repository/account_repository_impl.dart +++ b/lib/features/login/data/repository/account_repository_impl.dart @@ -22,4 +22,9 @@ class AccountRepositoryImpl extends AccountRepository { Future deleteCurrentAccount(String hashId) { return _accountDatasource.deleteCurrentAccount(hashId); } + + @override + Future> getAllAccount() { + return _accountDatasource.getAllAccount(); + } } \ No newline at end of file diff --git a/lib/features/login/domain/exceptions/authentication_exception.dart b/lib/features/login/domain/exceptions/authentication_exception.dart index 926d23f34b..aab6e7911d 100644 --- a/lib/features/login/domain/exceptions/authentication_exception.dart +++ b/lib/features/login/domain/exceptions/authentication_exception.dart @@ -25,6 +25,8 @@ class BadGateway extends AuthenticationException { class NotFoundAuthenticatedAccountException implements Exception {} +class NotFoundActiveAccountException implements Exception {} + class InvalidBaseUrl extends AuthenticationException { InvalidBaseUrl() : super(AuthenticationException.invalidBaseUrl); diff --git a/lib/features/login/domain/repository/account_repository.dart b/lib/features/login/domain/repository/account_repository.dart index d1d656e72f..d5d9827f51 100644 --- a/lib/features/login/domain/repository/account_repository.dart +++ b/lib/features/login/domain/repository/account_repository.dart @@ -7,4 +7,6 @@ abstract class AccountRepository { Future setCurrentAccount(PersonalAccount newCurrentAccount); Future deleteCurrentAccount(String hashId); + + Future> getAllAccount(); } \ No newline at end of file diff --git a/lib/features/login/domain/state/get_all_authenticated_account_state.dart b/lib/features/login/domain/state/get_all_authenticated_account_state.dart new file mode 100644 index 0000000000..3e99fd595f --- /dev/null +++ b/lib/features/login/domain/state/get_all_authenticated_account_state.dart @@ -0,0 +1,17 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:model/account/personal_account.dart'; + +class GetAllAuthenticatedAccountSuccess extends UIState { + final List listAccount; + + GetAllAuthenticatedAccountSuccess(this.listAccount); + + @override + List get props => [listAccount]; +} + +class GetAllAuthenticatedAccountFailure extends FeatureFailure { + + GetAllAuthenticatedAccountFailure(dynamic exception) : super(exception: exception); +} diff --git a/lib/features/login/domain/usecases/get_all_authenticated_account_interactor.dart b/lib/features/login/domain/usecases/get_all_authenticated_account_interactor.dart new file mode 100644 index 0000000000..49def607d5 --- /dev/null +++ b/lib/features/login/domain/usecases/get_all_authenticated_account_interactor.dart @@ -0,0 +1,20 @@ +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/login/domain/repository/account_repository.dart'; +import 'package:tmail_ui_user/features/login/domain/state/get_all_authenticated_account_state.dart'; + +class GetAllAuthenticatedAccountInteractor { + final AccountRepository _accountRepository; + + GetAllAuthenticatedAccountInteractor(this._accountRepository); + + Future> execute() async { + try { + final listAccount = await _accountRepository.getAllAccount(); + return Right(GetAllAuthenticatedAccountSuccess(listAccount)); + } catch (e) { + return Left(GetAllAuthenticatedAccountFailure(e)); + } + } +} \ No newline at end of file From 1d70c774b80268f56c6ee8c3fa4f014b9cd5d135 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 5 Jan 2024 19:35:54 +0700 Subject: [PATCH 02/17] TF-2431 Handle open multiple account picker Signed-off-by: dab246 --- assets/images/ic_logo_twake_horizontal.svg | 47 +++++++ .../presentation/resources/image_paths.dart | 1 + .../twake_mail_presentation_account.dart | 31 +++++ .../presentation/thread_controller.dart | 4 +- .../thread/presentation/thread_view.dart | 5 +- .../app_bar/app_bar_thread_widget.dart | 5 +- .../app_bar/mobile_app_bar_thread_widget.dart | 7 +- lib/l10n/intl_messages.arb | 6 + .../credential/credential_bindings.dart | 2 + lib/main/bindings/main_bindings.dart | 2 + .../bindings/manager/manager_bindings.dart | 16 +++ lib/main/localizations/app_localizations.dart | 7 ++ .../utils/authenticated_account_manager.dart | 119 ++++++++++++++++++ 13 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 assets/images/ic_logo_twake_horizontal.svg create mode 100644 lib/features/login/presentation/model/twake_mail_presentation_account.dart create mode 100644 lib/main/bindings/manager/manager_bindings.dart create mode 100644 lib/main/utils/authenticated_account_manager.dart diff --git a/assets/images/ic_logo_twake_horizontal.svg b/assets/images/ic_logo_twake_horizontal.svg new file mode 100644 index 0000000000..b4c397d8ce --- /dev/null +++ b/assets/images/ic_logo_twake_horizontal.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index e62d020a99..95ab996b21 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -211,6 +211,7 @@ class ImagePaths { String get icLogoTwakeWelcome => _getImagePath('ic_logo_twake_welcome.svg'); String get icCheckboxOn => _getImagePath('ic_checkbox_on.svg'); String get icCheckboxOff => _getImagePath('ic_checkbox_off.svg'); + String get icLogoTwakeHorizontal => _getImagePath('ic_logo_twake_horizontal.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/features/login/presentation/model/twake_mail_presentation_account.dart b/lib/features/login/presentation/model/twake_mail_presentation_account.dart new file mode 100644 index 0000000000..c78ad71109 --- /dev/null +++ b/lib/features/login/presentation/model/twake_mail_presentation_account.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/material.dart'; +import 'package:linagora_design_flutter/multiple_account/models/twake_presentation_account.dart'; +import 'package:model/account/personal_account.dart'; + +class TwakeMailPresentationAccount extends TwakePresentationAccount { + + final PersonalAccount personalAccount; + + const TwakeMailPresentationAccount({ + required this.personalAccount, + required String accountName, + required String accountId, + required Widget avatar, + required AccountActiveStatus accountActiveStatus, + }) : super( + accountName: accountName, + accountId: accountId, + avatar: avatar, + accountActiveStatus: accountActiveStatus, + ); + + @override + List get props => [ + personalAccount, + accountName, + accountId, + avatar, + accountActiveStatus, + ]; +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 6e4274cd72..adfa82d058 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -77,13 +77,15 @@ import 'package:tmail_ui_user/main/routes/dialog_router.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; +import 'package:tmail_ui_user/main/utils/authenticated_account_manager.dart'; typedef StartRangeSelection = int; typedef EndRangeSelection = int; class ThreadController extends BaseController with EmailActionController { - final networkConnectionController = Get.find(); + final NetworkConnectionController networkConnectionController = Get.find(); + final AuthenticatedAccountManager authenticatedAccountManager = Get.find(); final GetEmailsInMailboxInteractor _getEmailsInMailboxInteractor; final RefreshChangesEmailsInMailboxInteractor _refreshChangesEmailsInMailboxInteractor; diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 9db6e2a333..b1e95428f1 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -101,7 +101,10 @@ class ThreadView extends GetWidget (option) => controller.filterMessagesAction(context, option) ) ) - : null + : null, + onOpenAccountPickerAction: () async { + await controller.authenticatedAccountManager.showAccountsBottomSheetModal(context); + }, ); }), if (PlatformInfo.isMobile) diff --git a/lib/features/thread/presentation/widgets/app_bar/app_bar_thread_widget.dart b/lib/features/thread/presentation/widgets/app_bar/app_bar_thread_widget.dart index f2d518ef97..7bf080e58c 100644 --- a/lib/features/thread/presentation/widgets/app_bar/app_bar_thread_widget.dart +++ b/lib/features/thread/presentation/widgets/app_bar/app_bar_thread_widget.dart @@ -16,6 +16,7 @@ typedef OnEditThreadAction = void Function(); typedef OnOpenMailboxMenuActionClick = void Function(); typedef OnCancelEditThreadAction = void Function(); typedef OnEmailSelectionAction = void Function(EmailActionType, List); +typedef OnOpenAccountPickerAction = void Function(); class AppBarThreadWidget extends StatelessWidget { final OnPopupMenuFilterEmailAction? onPopupMenuFilterEmailAction; @@ -29,6 +30,7 @@ class AppBarThreadWidget extends StatelessWidget { final SelectMode selectMode; final FilterMessageOption filterOption; final UserProfile? userProfile; + final OnOpenAccountPickerAction? onOpenAccountPickerAction; const AppBarThreadWidget({ Key? key, @@ -43,6 +45,7 @@ class AppBarThreadWidget extends StatelessWidget { required this.userProfile, this.onPopupMenuFilterEmailAction, this.onContextMenuFilterEmailAction, + this.onOpenAccountPickerAction, }) : super(key: key); @override @@ -70,8 +73,8 @@ class AppBarThreadWidget extends StatelessWidget { selectMode: selectMode, filterOption: filterOption, openMailboxAction: openMailboxAction, - editThreadAction: editThreadAction, cancelEditThreadAction: cancelEditThreadAction, + onOpenAccountPickerAction: onOpenAccountPickerAction, ); } } diff --git a/lib/features/thread/presentation/widgets/app_bar/mobile_app_bar_thread_widget.dart b/lib/features/thread/presentation/widgets/app_bar/mobile_app_bar_thread_widget.dart index 1399bdd3f4..b35226d537 100644 --- a/lib/features/thread/presentation/widgets/app_bar/mobile_app_bar_thread_widget.dart +++ b/lib/features/thread/presentation/widgets/app_bar/mobile_app_bar_thread_widget.dart @@ -27,8 +27,8 @@ class MobileAppBarThreadWidget extends StatelessWidget { final SelectMode selectMode; final FilterMessageOption filterOption; final OnOpenMailboxMenuActionClick openMailboxAction; - final OnEditThreadAction editThreadAction; final OnCancelEditThreadAction cancelEditThreadAction; + final OnOpenAccountPickerAction? onOpenAccountPickerAction; MobileAppBarThreadWidget({ super.key, @@ -38,8 +38,8 @@ class MobileAppBarThreadWidget extends StatelessWidget { required this.selectMode, required this.filterOption, required this.openMailboxAction, - required this.editThreadAction, required this.cancelEditThreadAction, + required this.onOpenAccountPickerAction, }); @override @@ -97,7 +97,8 @@ class MobileAppBarThreadWidget extends StatelessWidget { blurRadius: 1, offset: Offset(0, 0.5) ) - ] + ], + onTapAction: onOpenAccountPickerAction, ), ], ); diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 95ff6fc154..c7f3f932bc 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -3665,5 +3665,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "addAnotherAccount": "Add another account", + "@addAnotherAccount": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/bindings/credential/credential_bindings.dart b/lib/main/bindings/credential/credential_bindings.dart index de04145d42..c963c56e4d 100644 --- a/lib/main/bindings/credential/credential_bindings.dart +++ b/lib/main/bindings/credential/credential_bindings.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/login/domain/repository/account_repositor import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_repository.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/get_all_authenticated_account_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/logout_current_account_basic_auth_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_interactor.dart'; @@ -39,6 +40,7 @@ class CredentialBindings extends InteractorsBindings { Get.find(), )); Get.put(GetAuthenticatedAccountInteractor(Get.find())); + Get.put(GetAllAuthenticatedAccountInteractor(Get.find())); Get.put(AuthenticationInteractor( Get.find(), Get.find() diff --git a/lib/main/bindings/main_bindings.dart b/lib/main/bindings/main_bindings.dart index 358a6df72f..eb7640d178 100644 --- a/lib/main/bindings/main_bindings.dart +++ b/lib/main/bindings/main_bindings.dart @@ -3,6 +3,7 @@ import 'package:tmail_ui_user/main/bindings/core/core_bindings.dart'; import 'package:tmail_ui_user/main/bindings/credential/credential_bindings.dart'; import 'package:tmail_ui_user/main/bindings/local/local_bindings.dart'; import 'package:tmail_ui_user/main/bindings/local/local_isolate_bindings.dart'; +import 'package:tmail_ui_user/main/bindings/manager/manager_bindings.dart'; import 'package:tmail_ui_user/main/bindings/network/network_bindings.dart'; import 'package:tmail_ui_user/main/bindings/network/network_isolate_binding.dart'; import 'package:tmail_ui_user/main/bindings/network_connection/network_connection_bindings.dart'; @@ -19,5 +20,6 @@ class MainBindings extends Bindings { CredentialBindings().dependencies(); SessionBindings().dependencies(); NetWorkConnectionBindings().dependencies(); + ManagerBindings().dependencies(); } } \ No newline at end of file diff --git a/lib/main/bindings/manager/manager_bindings.dart b/lib/main/bindings/manager/manager_bindings.dart new file mode 100644 index 0000000000..a9f4618111 --- /dev/null +++ b/lib/main/bindings/manager/manager_bindings.dart @@ -0,0 +1,16 @@ + +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/get_all_authenticated_account_interactor.dart'; +import 'package:tmail_ui_user/main/utils/authenticated_account_manager.dart'; + +class ManagerBindings extends Bindings { + + @override + void dependencies() { + Get.put(AuthenticatedAccountManager( + Get.find(), + Get.find(), + )); + } +} \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 271d6f05c4..05c8d19930 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3821,4 +3821,11 @@ class AppLocalizations { name: 'descriptionTwakeId', ); } + + String get addAnotherAccount { + return Intl.message( + 'Add another account', + name: 'addAnotherAccount', + ); + } } \ No newline at end of file diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart new file mode 100644 index 0000000000..14c7b5cb28 --- /dev/null +++ b/lib/main/utils/authenticated_account_manager.dart @@ -0,0 +1,119 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/extensions/string_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:core/presentation/views/image/avatar_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:linagora_design_flutter/multiple_account/models/twake_presentation_account.dart'; +import 'package:linagora_design_flutter/multiple_account/multiple_account_picker.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:tmail_ui_user/features/login/domain/state/get_all_authenticated_account_state.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/get_all_authenticated_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/twake_mail_presentation_account.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class AuthenticatedAccountManager { + + final GetAllAuthenticatedAccountInteractor _getAllAuthenticatedAccountInteractor; + final ImagePaths _imagePaths; + + AuthenticatedAccountManager( + this._getAllAuthenticatedAccountInteractor, + this._imagePaths, + ); + + Future> _getAllTwakeMailPresentationAccount() { + return _getAllAuthenticatedAccountInteractor + .execute() + .then((result) => result.fold( + (failure) => [], + (success) => success is GetAllAuthenticatedAccountSuccess + ? _generateTwakePresentationAccount(success.listAccount) + : [] + ) + ); + } + + List _generateTwakePresentationAccount(List listAccount) { + final listPresentationAccount = listAccount + .map((account) => TwakeMailPresentationAccount( + personalAccount: account, + accountId: account.userName?.value ?? '', + accountName: account.userName?.value ?? '', + accountActiveStatus: account.isSelected + ? AccountActiveStatus.active + : AccountActiveStatus.inactive, + avatar: AvatarBuilder( + text: account.userName?.value.isNotEmpty == true + ? account.userName!.value.firstLetterToUpperCase + : '', + size: 56, + textColor: Colors.black, + bgColor: Colors.white, + boxShadows: const [ + BoxShadow( + color: AppColor.colorShadowBgContentEmail, + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 0.5) + ) + ] + ) + )) + .toList(); + + listPresentationAccount.sort((pre, next) => pre.accountActiveStatus.index.compareTo(next.accountActiveStatus.index)); + + return listPresentationAccount; + } + + Future showAccountsBottomSheetModal(BuildContext context) async { + final listPresentationAccount = await _getAllTwakeMailPresentationAccount(); + + if (context.mounted) { + await MultipleAccountPicker.showMultipleAccountPicker( + accounts: listPresentationAccount, + context: context, + titleAddAnotherAccount: AppLocalizations.of(context).addAnotherAccount, + titleAccountSettings: AppLocalizations.of(context).manage_account, + logoApp: Stack( + children: [ + Center(child: Padding( + padding: const EdgeInsetsDirectional.symmetric(vertical: 16), + child: SvgPicture.asset(_imagePaths.icLogoTwakeHorizontal), + )), + Align( + alignment: AlignmentDirectional.centerEnd, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icComposerClose, + iconColor: Colors.black, + margin: const EdgeInsetsDirectional.only(top: 8, end: 8), + backgroundColor: Colors.transparent, + onTapActionCallback: popBack, + ), + ) + ] + ), + accountNameStyle: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: LinagoraSysColors.material().onSurface, + ), + accountIdStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: LinagoraRefColors.material().tertiary[20], + ), + addAnotherAccountStyle: Theme.of(context).textTheme.labelLarge!.copyWith( + color: LinagoraSysColors.material().onPrimary, + ), + titleAccountSettingsStyle: Theme.of(context).textTheme.labelLarge!.copyWith( + color: LinagoraSysColors.material().primary, + ), + onAddAnotherAccount: () {}, + onGoToAccountSettings: () {}, + onSetAccountAsActive: (presentationAccount) {}, + ); + } + } +} \ No newline at end of file From 3bfc2ef77cee56028a1baacf35a0e24c6e2aa084 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 5 Jan 2024 19:41:08 +0700 Subject: [PATCH 03/17] TF-2431 Navigate to Setting View from multiple account picker Signed-off-by: dab246 --- lib/features/thread/presentation/thread_view.dart | 5 ++++- lib/main/utils/authenticated_account_manager.dart | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index b1e95428f1..33a66b0d88 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -103,7 +103,10 @@ class ThreadView extends GetWidget ) : null, onOpenAccountPickerAction: () async { - await controller.authenticatedAccountManager.showAccountsBottomSheetModal(context); + await controller.authenticatedAccountManager.showAccountsBottomSheetModal( + context: context, + onGoToManageAccount: controller.mailboxDashBoardController.goToSettings + ); }, ); }), diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart index 14c7b5cb28..b9fa9cd270 100644 --- a/lib/main/utils/authenticated_account_manager.dart +++ b/lib/main/utils/authenticated_account_manager.dart @@ -71,7 +71,10 @@ class AuthenticatedAccountManager { return listPresentationAccount; } - Future showAccountsBottomSheetModal(BuildContext context) async { + Future showAccountsBottomSheetModal({ + required BuildContext context, + VoidCallback? onGoToManageAccount, + }) async { final listPresentationAccount = await _getAllTwakeMailPresentationAccount(); if (context.mounted) { @@ -111,7 +114,7 @@ class AuthenticatedAccountManager { color: LinagoraSysColors.material().primary, ), onAddAnotherAccount: () {}, - onGoToAccountSettings: () {}, + onGoToAccountSettings: () => onGoToManageAccount?.call(), onSetAccountAsActive: (presentationAccount) {}, ); } From e56111cd22ca9d32f1ade86b210a2c13115f9797 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 5 Jan 2024 20:08:18 +0700 Subject: [PATCH 04/17] TF-2431 Navigate to TwakeIdView when click AddAnotherAccount button Signed-off-by: dab246 --- core/lib/presentation/utils/theme_utils.dart | 5 +++ .../model/login_navigate_arguments.dart | 15 ++++++++ .../model/login_navigate_type.dart | 5 +++ .../twake_id/twake_id_controller.dart | 12 +++++++ .../presentation/twake_id/twake_id_view.dart | 34 +++++++++++++------ .../thread/presentation/thread_view.dart | 14 +++++++- .../utils/authenticated_account_manager.dart | 8 ++++- 7 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 lib/features/login/presentation/model/login_navigate_arguments.dart create mode 100644 lib/features/login/presentation/model/login_navigate_type.dart diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index 83058a04af..bfa024504e 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -34,6 +34,7 @@ class ThemeUtils { ), bodyMedium: TextStyle( fontWeight: FontWeight.w500, + fontSize: 15 ), bodySmall: TextStyle( fontWeight: FontWeight.w500 @@ -44,6 +45,10 @@ class ThemeUtils { ), labelSmall: TextStyle( fontWeight: FontWeight.normal + ), + labelLarge: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16 ) ); } diff --git a/lib/features/login/presentation/model/login_navigate_arguments.dart b/lib/features/login/presentation/model/login_navigate_arguments.dart new file mode 100644 index 0000000000..0c7026155e --- /dev/null +++ b/lib/features/login/presentation/model/login_navigate_arguments.dart @@ -0,0 +1,15 @@ + +import 'package:model/account/personal_account.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; +import 'package:tmail_ui_user/main/routes/router_arguments.dart'; + +class LoginNavigateArguments extends RouterArguments { + + final LoginNavigateType navigateType; + final PersonalAccount? currentAccount; + + LoginNavigateArguments(this.navigateType, this.currentAccount); + + @override + List get props => [navigateType, currentAccount]; +} \ No newline at end of file diff --git a/lib/features/login/presentation/model/login_navigate_type.dart b/lib/features/login/presentation/model/login_navigate_type.dart new file mode 100644 index 0000000000..b29a26df15 --- /dev/null +++ b/lib/features/login/presentation/model/login_navigate_type.dart @@ -0,0 +1,5 @@ + +enum LoginNavigateType { + signIn, + addAnotherAccount; +} \ No newline at end of file diff --git a/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart b/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart index 838805be5d..b4c545896e 100644 --- a/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart +++ b/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart @@ -1,19 +1,31 @@ +import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/utils/theme_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class TwakeIdController extends GetxController { + final ImagePaths imagePaths = Get.find(); + + final loginNavigateArguments = Rxn(); + @override void onInit() { ThemeUtils.setPreferredPortraitOrientations(); super.onInit(); } + @override + void onReady() { + super.onReady(); + loginNavigateArguments.value = Get.arguments; + } + void handleUseCompanyServer() { popAndPush( AppRoutes.login, diff --git a/lib/features/starting_page/presentation/twake_id/twake_id_view.dart b/lib/features/starting_page/presentation/twake_id/twake_id_view.dart index cd8fbdddc0..6d27648bda 100644 --- a/lib/features/starting_page/presentation/twake_id/twake_id_view.dart +++ b/lib/features/starting_page/presentation/twake_id/twake_id_view.dart @@ -1,8 +1,10 @@ +import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:tmail_ui_user/features/starting_page/presentation/twake_id/twake_id_controller.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class TwakeIdView extends GetWidget { @@ -10,16 +12,26 @@ class TwakeIdView extends GetWidget { @override Widget build(BuildContext context) { - return TwakeIdScreen( - focusColor: Colors.transparent, - hoverColor: Colors.transparent, - highlightColor: Colors.transparent, - overlayColor: MaterialStateProperty.all(Colors.transparent), - signInTitle: AppLocalizations.of(context).signIn.capitalizeFirst ?? '', - createTwakeIdTitle: AppLocalizations.of(context).createTwakeId, - useCompanyServerTitle: AppLocalizations.of(context).useCompanyServer, - description: AppLocalizations.of(context).descriptionTwakeId, - onUseCompanyServerOnTap: controller.handleUseCompanyServer, - ); + return Obx(() { + return TwakeIdScreen( + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + overlayColor: MaterialStateProperty.all(Colors.transparent), + signInTitle: AppLocalizations.of(context).signIn.capitalizeFirst ?? '', + createTwakeIdTitle: AppLocalizations.of(context).createTwakeId, + useCompanyServerTitle: AppLocalizations.of(context).useCompanyServer, + description: AppLocalizations.of(context).descriptionTwakeId, + onUseCompanyServerOnTap: controller.handleUseCompanyServer, + backButton: controller.loginNavigateArguments.value != null + ? TMailButtonWidget.fromIcon( + icon: controller.imagePaths.icArrowLeft, + backgroundColor: Colors.transparent, + margin: const EdgeInsetsDirectional.only(start: 8), + iconColor: Colors.black, + onTapActionCallback: popBack) + : null, + ); + }); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 33a66b0d88..546af811cd 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -11,6 +11,8 @@ import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart' import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; 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'; @@ -40,6 +42,7 @@ import 'package:tmail_ui_user/features/thread/presentation/widgets/spam_banner/s import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_bottom_loading_bar_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_loading_bar_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ThreadView extends GetWidget @@ -105,7 +108,16 @@ class ThreadView extends GetWidget onOpenAccountPickerAction: () async { await controller.authenticatedAccountManager.showAccountsBottomSheetModal( context: context, - onGoToManageAccount: controller.mailboxDashBoardController.goToSettings + onGoToManageAccount: controller.mailboxDashBoardController.goToSettings, + onAddAnotherAccountAction: (currentAccount) { + push( + AppRoutes.twakeId, + arguments: LoginNavigateArguments( + LoginNavigateType.addAnotherAccount, + currentAccount + ) + ); + } ); }, ); diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart index b9fa9cd270..c8bd6f1a17 100644 --- a/lib/main/utils/authenticated_account_manager.dart +++ b/lib/main/utils/authenticated_account_manager.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/string_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; @@ -16,6 +17,8 @@ import 'package:tmail_ui_user/features/login/presentation/model/twake_mail_prese import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; +typedef OnAddAnotherAccountAction = Function(PersonalAccount? currentAccount); + class AuthenticatedAccountManager { final GetAllAuthenticatedAccountInteractor _getAllAuthenticatedAccountInteractor; @@ -74,8 +77,11 @@ class AuthenticatedAccountManager { Future showAccountsBottomSheetModal({ required BuildContext context, VoidCallback? onGoToManageAccount, + OnAddAnotherAccountAction? onAddAnotherAccountAction, }) async { final listPresentationAccount = await _getAllTwakeMailPresentationAccount(); + final activeAccount = listPresentationAccount + .firstWhereOrNull((presentationAccount) => presentationAccount.isActive); if (context.mounted) { await MultipleAccountPicker.showMultipleAccountPicker( @@ -113,7 +119,7 @@ class AuthenticatedAccountManager { titleAccountSettingsStyle: Theme.of(context).textTheme.labelLarge!.copyWith( color: LinagoraSysColors.material().primary, ), - onAddAnotherAccount: () {}, + onAddAnotherAccount: () => onAddAnotherAccountAction?.call(activeAccount?.personalAccount), onGoToAccountSettings: () => onGoToManageAccount?.call(), onSetAccountAsActive: (presentationAccount) {}, ); From 5dedb819fcf449b798c6b0beee9eb3f1b33ae122 Mon Sep 17 00:00:00 2001 From: dab246 Date: Sat, 6 Jan 2024 01:26:57 +0700 Subject: [PATCH 05/17] TF-2431 Reload new data when add another account success Signed-off-by: dab246 --- .../login/presentation/login_controller.dart | 22 +++++++++++++++---- .../mailbox_dashboard_controller.dart | 6 ----- .../twake_id/twake_id_controller.dart | 20 ++++++++++++----- .../presentation/twake_id/twake_id_view.dart | 2 +- .../presentation/thread_controller.dart | 12 ++++++++++ .../thread/presentation/thread_view.dart | 13 +---------- 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index 93ce51ae6c..38098c7e1a 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -45,6 +45,8 @@ import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_m import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.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'; @@ -77,6 +79,7 @@ class LoginController extends ReloadableController { UserName? _username; Password? _password; Uri? _baseUri; + LoginNavigateArguments? _navigateArguments; LoginController( this._authenticationInteractor, @@ -113,6 +116,9 @@ class LoginController extends ReloadableController { if (PlatformInfo.isWeb) { _checkOIDCIsAvailable(); } + } else if (PlatformInfo.isMobile && arguments is LoginNavigateArguments) { + loginFormType.value = LoginFormType.dnsLookupForm; + _navigateArguments = arguments; } else if (PlatformInfo.isWeb) { _handleAuthenticationSSOBrowserCallbackAction(); } @@ -190,12 +196,20 @@ class LoginController extends ReloadableController { @override void handleReloaded(Session session) { - popAndPush( - RouteUtils.generateNavigationRoute(AppRoutes.dashboard), - arguments: session - ); + if (PlatformInfo.isMobile && _isAddAnotherAccount) { + pushAndPopAll( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: session); + } else { + popAndPush( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: session); + } } + bool get _isAddAnotherAccount => _navigateArguments != null && + _navigateArguments?.navigateType == LoginNavigateType.addAnotherAccount; + void _handleAuthenticationSSOBrowserCallbackAction() { consumeState(_getAuthResponseUrlBrowserInteractor.execute()); } 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 42d277d407..b4ae2544a5 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -523,12 +523,6 @@ class MailboxDashBoardController extends ReloadableController { log('MailboxDashBoardController::_handleSession:'); _setUpComponentsFromSession(session); - updateAuthenticationAccount( - sessionCurrent!, - accountId.value!, - sessionCurrent!.username - ); - if (PlatformInfo.isMobile && !_notificationManager.isNotificationClickedOnTerminate) { _handleClickLocalNotificationOnTerminated(); } else { diff --git a/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart b/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart index b4c545896e..74e3c37c09 100644 --- a/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart +++ b/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; @@ -12,7 +13,7 @@ class TwakeIdController extends GetxController { final ImagePaths imagePaths = Get.find(); - final loginNavigateArguments = Rxn(); + final navigateArguments = Rxn(); @override void onInit() { @@ -23,12 +24,21 @@ class TwakeIdController extends GetxController { @override void onReady() { super.onReady(); - loginNavigateArguments.value = Get.arguments; + navigateArguments.value = Get.arguments; } void handleUseCompanyServer() { - popAndPush( - AppRoutes.login, - arguments: LoginArguments(LoginFormType.dnsLookupForm)); + if (isAddAnotherAccount) { + push( + AppRoutes.login, + arguments: navigateArguments.value); + } else { + popAndPush( + AppRoutes.login, + arguments: LoginArguments(LoginFormType.dnsLookupForm)); + } } + + bool get isAddAnotherAccount => navigateArguments.value != null && + navigateArguments.value?.navigateType == LoginNavigateType.addAnotherAccount; } \ No newline at end of file diff --git a/lib/features/starting_page/presentation/twake_id/twake_id_view.dart b/lib/features/starting_page/presentation/twake_id/twake_id_view.dart index 6d27648bda..20ec6316dd 100644 --- a/lib/features/starting_page/presentation/twake_id/twake_id_view.dart +++ b/lib/features/starting_page/presentation/twake_id/twake_id_view.dart @@ -23,7 +23,7 @@ class TwakeIdView extends GetWidget { useCompanyServerTitle: AppLocalizations.of(context).useCompanyServer, description: AppLocalizations.of(context).descriptionTwakeId, onUseCompanyServerOnTap: controller.handleUseCompanyServer, - backButton: controller.loginNavigateArguments.value != null + backButton: controller.isAddAnotherAccount ? TMailButtonWidget.fromIcon( icon: controller.imagePaths.icArrowLeft, backgroundColor: Colors.transparent, diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index adfa82d058..b2ccd6f931 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -29,6 +29,8 @@ import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state. import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; @@ -1243,4 +1245,14 @@ class ThreadController extends BaseController with EmailActionController { _searchEmail(); mailboxDashBoardController.clearDashBoardAction(); } + + void addAnotherAccount(PersonalAccount? currentAccount) { + push( + AppRoutes.twakeId, + arguments: LoginNavigateArguments( + LoginNavigateType.addAnotherAccount, + currentAccount + ) + ); + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 546af811cd..ea8158d240 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -11,8 +11,6 @@ import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart' import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart'; -import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; -import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; 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'; @@ -42,7 +40,6 @@ import 'package:tmail_ui_user/features/thread/presentation/widgets/spam_banner/s import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_bottom_loading_bar_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_loading_bar_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ThreadView extends GetWidget @@ -109,15 +106,7 @@ class ThreadView extends GetWidget await controller.authenticatedAccountManager.showAccountsBottomSheetModal( context: context, onGoToManageAccount: controller.mailboxDashBoardController.goToSettings, - onAddAnotherAccountAction: (currentAccount) { - push( - AppRoutes.twakeId, - arguments: LoginNavigateArguments( - LoginNavigateType.addAnotherAccount, - currentAccount - ) - ); - } + onAddAnotherAccountAction: controller.addAnotherAccount ); }, ); From 9e9720effe12a8c1ef61f356b705328ba2367a7b Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 8 Jan 2024 19:41:34 +0700 Subject: [PATCH 06/17] TF-2431 Handle switch active account Signed-off-by: dab246 --- lib/features/base/base_controller.dart | 37 ++++++ .../reloadable/reloadable_controller.dart | 62 +++------- .../home/presentation/home_controller.dart | 108 ++++++++++++++++-- .../data/datasource/account_datasource.dart | 2 + .../hive_account_datasource_impl.dart | 7 ++ .../personal_account_extension.dart | 6 +- .../data/local/account_cache_manager.dart | 14 +++ .../repository/account_repository_impl.dart | 5 + .../domain/repository/account_repository.dart | 2 + ...dd_account_id_to_active_account_state.dart | 11 ++ .../set_current_account_active_state.dart | 11 ++ .../update_authentication_account_state.dart | 11 -- ...ount_id_to_active_account_interactor.dart} | 14 +-- ...set_current_account_active_interactor.dart | 22 ++++ .../login/presentation/login_controller.dart | 6 +- .../model/login_navigate_arguments.dart | 17 ++- .../model/login_navigate_type.dart | 3 +- .../mailbox_dashboard_controller.dart | 63 ++++++++-- .../restore_active_account_arguments.dart | 21 ++++ .../switch_active_account_arguments.dart | 24 ++++ .../twake_id/twake_id_controller.dart | 4 + .../presentation/twake_id/twake_id_view.dart | 3 +- .../presentation/thread_controller.dart | 24 +++- .../thread/presentation/thread_view.dart | 3 +- lib/l10n/intl_messages.arb | 8 +- .../credential/credential_bindings.dart | 6 +- lib/main/localizations/app_localizations.dart | 7 ++ .../utils/authenticated_account_manager.dart | 16 ++- 28 files changed, 418 insertions(+), 99 deletions(-) create mode 100644 lib/features/login/domain/state/add_account_id_to_active_account_state.dart create mode 100644 lib/features/login/domain/state/set_current_account_active_state.dart delete mode 100644 lib/features/login/domain/state/update_authentication_account_state.dart rename lib/features/login/domain/usecases/{update_authentication_account_interactor.dart => add_account_id_to_active_account_interactor.dart} (67%) create mode 100644 lib/features/login/domain/usecases/set_current_account_active_interactor.dart create mode 100644 lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart create mode 100644 lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 581a0a45e1..246b707b73 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -25,11 +25,13 @@ import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.da import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.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/extensions/token_oidc_extension.dart'; import 'package:tmail_ui_user/features/login/data/network/config/authorization_interceptors.dart'; import 'package:tmail_ui_user/features/login/domain/state/logout_current_account_basic_auth_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/logout_current_account_oidc_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/logout_current_account_state.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/set_current_account_active_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/contact_autocomplete_bindings.dart'; @@ -75,6 +77,7 @@ abstract class BaseController extends GetxController final ResponsiveUtils responsiveUtils = Get.find(); final Uuid uuid = Get.find(); final AppStore appStore = Get.find(); + final SetCurrentAccountActiveInteractor _setCurrentAccountActiveInteractor = Get.find(); final _fcmReceiver = FcmReceiver.instance; bool _isFcmEnabled = false; @@ -385,4 +388,38 @@ abstract class BaseController extends GetxController await clearDataAndGoToLoginPage(); } } + + void setUpInterceptors(PersonalAccount personalAccount) { + dynamicUrlInterceptors.setJmapUrl(personalAccount.baseUrl); + dynamicUrlInterceptors.changeBaseUrl(personalAccount.baseUrl); + + switch(personalAccount.authType) { + case AuthenticationType.oidc: + authorizationInterceptors.setTokenAndAuthorityOidc( + newToken: personalAccount.tokenOidc, + newConfig: personalAccount.tokenOidc!.oidcConfiguration + ); + authorizationIsolateInterceptors.setTokenAndAuthorityOidc( + newToken: personalAccount.tokenOidc, + newConfig: personalAccount.tokenOidc!.oidcConfiguration + ); + break; + case AuthenticationType.basic: + authorizationInterceptors.setBasicAuthorization( + personalAccount.basicAuth!.userName, + personalAccount.basicAuth!.password, + ); + authorizationIsolateInterceptors.setBasicAuthorization( + personalAccount.basicAuth!.userName, + personalAccount.basicAuth!.password, + ); + break; + default: + break; + } + } + + void setCurrentAccountActive(PersonalAccount activeAccount) { + consumeState(_setCurrentAccountActiveInteractor.execute(activeAccount)); + } } diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index fc00649c3c..21bb7d8909 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -5,17 +5,14 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.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/authentication_type.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'; 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/data/extensions/token_oidc_extension.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_authenticated_account_state.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/add_account_id_to_active_account_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/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -23,9 +20,9 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/utils/message_toast_utils.dart'; abstract class ReloadableController extends BaseController { - final GetSessionInteractor _getSessionInteractor = Get.find(); + final GetSessionInteractor getSessionInteractor = Get.find(); final GetAuthenticatedAccountInteractor _getAuthenticatedAccountInteractor = Get.find(); - final UpdateAuthenticationAccountInteractor _updateAuthenticationAccountInteractor = Get.find(); + final AddAccountIdToActiveAccountInteractor _addAccountIdToActiveAccountInteractor = Get.find(); @override void handleFailureViewState(Failure failure) { @@ -68,38 +65,8 @@ abstract class ReloadableController extends BaseController { consumeState(_getAuthenticatedAccountInteractor.execute()); } - void setUpInterceptors(PersonalAccount personalAccount) { - dynamicUrlInterceptors.setJmapUrl(personalAccount.baseUrl); - dynamicUrlInterceptors.changeBaseUrl(personalAccount.baseUrl); - - switch(personalAccount.authType) { - case AuthenticationType.oidc: - authorizationInterceptors.setTokenAndAuthorityOidc( - newToken: personalAccount.tokenOidc, - newConfig: personalAccount.tokenOidc!.oidcConfiguration - ); - authorizationIsolateInterceptors.setTokenAndAuthorityOidc( - newToken: personalAccount.tokenOidc, - newConfig: personalAccount.tokenOidc!.oidcConfiguration - ); - break; - case AuthenticationType.basic: - authorizationInterceptors.setBasicAuthorization( - personalAccount.basicAuth!.userName, - personalAccount.basicAuth!.password, - ); - authorizationIsolateInterceptors.setBasicAuthorization( - personalAccount.basicAuth!.userName, - personalAccount.basicAuth!.password, - ); - break; - default: - break; - } - } - void getSessionAction({AccountId? accountId, UserName? userName}) { - consumeState(_getSessionInteractor.execute( + consumeState(getSessionInteractor.execute( accountId: accountId, userName: userName )); @@ -121,7 +88,11 @@ abstract class ReloadableController extends BaseController { final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl); if (apiUrl.isNotEmpty) { dynamicUrlInterceptors.changeBaseUrl(apiUrl); - updateAuthenticationAccount(session, personalAccount.accountId, session.username); + _addAccountIdToActiveAccount( + personalAccount.accountId, + session.username, + apiUrl + ); handleReloaded(session); } else { clearDataAndGoToLoginPage(); @@ -130,10 +101,15 @@ abstract class ReloadableController extends BaseController { void handleReloaded(Session session) {} - 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 _addAccountIdToActiveAccount( + AccountId accountId, + UserName userName, + String apiUrl, + ) { + consumeState(_addAccountIdToActiveAccountInteractor.execute( + accountId, + apiUrl, + userName + )); } } \ 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 e3b3d7b788..9ca03b014c 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:core/presentation/utils/theme_utils.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'; @@ -22,7 +25,12 @@ 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_state.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.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/restore_active_account_arguments.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/switch_active_account_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'; @@ -45,8 +53,8 @@ class HomeController extends ReloadableController { this._cleanupRecentLoginUsernameCacheInteractor, ); - PersonalAccount? currentAccount; EmailId? _emailIdPreview; + StreamSubscription? _sessionStreamSubscription; @override void onInit() { @@ -68,6 +76,12 @@ class HomeController extends ReloadableController { super.onReady(); } + @override + void onClose() { + _sessionStreamSubscription?.cancel(); + super.onClose(); + } + @override void handleReloaded(Session session) { if (_emailIdPreview != null) { @@ -87,20 +101,26 @@ class HomeController extends ReloadableController { } void _initFlutterDownloader() { - FlutterDownloader - .initialize(debug: kDebugMode) - .then((_) => FlutterDownloader.registerCallback(downloadCallback)); + if (!FlutterDownloader.initialized) { + FlutterDownloader + .initialize(debug: kDebugMode) + .then((_) => FlutterDownloader.registerCallback(downloadCallback)); + } } static void downloadCallback(String id, DownloadTaskStatus status, int progress) {} void _handleNavigateToScreen() async { if (PlatformInfo.isMobile) { - final firstTimeAppLaunch = await appStore.getItemBoolean(AppConfig.firstTimeAppLaunchKey); - if (firstTimeAppLaunch) { - await _cleanupCache(); + if (Get.arguments is LoginNavigateArguments) { + _handleLoginNavigateArguments(Get.arguments); } else { - _navigateToTwakeWelcomePage(); + final firstTimeAppLaunch = await appStore.getItemBoolean(AppConfig.firstTimeAppLaunchKey); + if (firstTimeAppLaunch) { + await _cleanupCache(); + } else { + _navigateToTwakeWelcomePage(); + } } } else { await _cleanupCache(); @@ -151,4 +171,76 @@ class HomeController extends ReloadableController { } } } + + void _handleLoginNavigateArguments(LoginNavigateArguments navigateArguments) async { + if (navigateArguments.navigateType == LoginNavigateType.switchActiveAccount) { + _switchActiveAccount( + navigateArguments.currentAccount!, + navigateArguments.sessionCurrentAccount!, + navigateArguments.nextActiveAccount!); + } else { + await _cleanupCache(); + } + } + + void _switchActiveAccount( + PersonalAccount currentActiveAccount, + Session sessionCurrentAccount, + PersonalAccount nextActiveAccount + ) { + setUpInterceptors(nextActiveAccount); + + _sessionStreamSubscription = getSessionInteractor.execute( + accountId: nextActiveAccount.accountId, + userName: nextActiveAccount.userName + ).listen( + (viewState) { + viewState.fold( + (failure) => _handleGetSessionFailureWhenSwitchActiveAccount( + currentActiveAccount: currentActiveAccount, + session: sessionCurrentAccount, + exception: failure), + (success) => success is GetSessionSuccess + ? _handleGetSessionSuccessWhenSwitchActiveAccount(nextActiveAccount, success.session) + : null, + ); + }, + onError: (error, stack) { + logError('HomeController::_switchActiveAccount:Exception: $error | Stack: $stack'); + _handleGetSessionFailureWhenSwitchActiveAccount( + currentActiveAccount: currentActiveAccount, + session: sessionCurrentAccount, + exception: error); + } + ); + } + + void _handleGetSessionSuccessWhenSwitchActiveAccount( + PersonalAccount nextActiveAccount, + Session sessionActiveAccount + ) async { + log('HomeController::_handleGetSessionSuccessWhenSwitchActiveAccount:sessionActiveAccount: $sessionActiveAccount'); + await popAndPush( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: SwitchActiveAccountArguments( + session: sessionActiveAccount, + nextActiveAccount: nextActiveAccount, + ) + ); + } + + void _handleGetSessionFailureWhenSwitchActiveAccount({ + required PersonalAccount currentActiveAccount, + required Session session, + dynamic exception + }) async { + logError('HomeController::_handleGetSessionFailureWhenSwitchActiveAccount:exception: $exception'); + await popAndPush( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: RestoreActiveAccountArguments( + currentAccount: currentActiveAccount, + session: session + ) + ); + } } \ No newline at end of file diff --git a/lib/features/login/data/datasource/account_datasource.dart b/lib/features/login/data/datasource/account_datasource.dart index 3a1870d4cc..0d704835bd 100644 --- a/lib/features/login/data/datasource/account_datasource.dart +++ b/lib/features/login/data/datasource/account_datasource.dart @@ -8,4 +8,6 @@ abstract class AccountDatasource { Future deleteCurrentAccount(String accountId); Future> getAllAccount(); + + Future setCurrentAccountActive(PersonalAccount activeAccount); } \ No newline at end of file diff --git a/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart b/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart index 162f0ebd00..135df9527a 100644 --- a/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart +++ b/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart @@ -48,4 +48,11 @@ class HiveAccountDatasourceImpl extends AccountDatasource { return await _accountCacheManager.getAllAccount(); }).catchError(_exceptionThrower.throwException); } + + @override + Future setCurrentAccountActive(PersonalAccount activeAccount) { + return Future.sync(() async { + return await _accountCacheManager.setCurrentAccountActive(activeAccount); + }).catchError(_exceptionThrower.throwException); + } } \ 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 0106ca2aa8..76fbd6fb4c 100644 --- a/lib/features/login/data/extensions/personal_account_extension.dart +++ b/lib/features/login/data/extensions/personal_account_extension.dart @@ -8,11 +8,11 @@ import 'package:tmail_ui_user/features/login/data/extensions/token_oidc_extensio import 'package:tmail_ui_user/features/login/data/model/account_cache.dart'; extension PersonalAccountExtension on PersonalAccount { - AccountCache toCache() { + AccountCache toCache({bool? isSelected}) { return AccountCache( id: id, authType: authType.name, - isSelected: isSelected, + isSelected: isSelected ?? this.isSelected, baseUrl: baseUrl, accountId: accountId?.id.value, apiUrl: apiUrl, @@ -36,7 +36,7 @@ extension PersonalAccountExtension on PersonalAccount { ); } - PersonalAccount updateAccountId({ + PersonalAccount addAccountId({ required AccountId accountId, required String apiUrl, required UserName userName, diff --git a/lib/features/login/data/local/account_cache_manager.dart b/lib/features/login/data/local/account_cache_manager.dart index 69b37baa48..8eb31b7990 100644 --- a/lib/features/login/data/local/account_cache_manager.dart +++ b/lib/features/login/data/local/account_cache_manager.dart @@ -57,4 +57,18 @@ class AccountCacheManager { throw NotFoundAuthenticatedAccountException(); } } + + Future setCurrentAccountActive(PersonalAccount activeAccount) async { + log('AccountCacheManager::setCurrentAccountActive(): $activeAccount'); + final newAccountCache = activeAccount.toCache(isSelected: true); + final allAccounts = await _accountCacheClient.getAll(); + log('AccountCacheManager::setCurrentAccountActive::allAccounts(): $allAccounts'); + if (allAccounts.isNotEmpty) { + final newAllAccounts = allAccounts.unselected().toList(); + if (newAllAccounts.isNotEmpty) { + await _accountCacheClient.updateMultipleItem(newAllAccounts.toMap()); + } + } + return _accountCacheClient.insertItem(newAccountCache.id, newAccountCache); + } } \ No newline at end of file diff --git a/lib/features/login/data/repository/account_repository_impl.dart b/lib/features/login/data/repository/account_repository_impl.dart index 4b6aceb8b1..6c496419ff 100644 --- a/lib/features/login/data/repository/account_repository_impl.dart +++ b/lib/features/login/data/repository/account_repository_impl.dart @@ -27,4 +27,9 @@ class AccountRepositoryImpl extends AccountRepository { Future> getAllAccount() { return _accountDatasource.getAllAccount(); } + + @override + Future setCurrentAccountActive(PersonalAccount activeAccount) { + return _accountDatasource.setCurrentAccountActive(activeAccount); + } } \ No newline at end of file diff --git a/lib/features/login/domain/repository/account_repository.dart b/lib/features/login/domain/repository/account_repository.dart index d5d9827f51..63bd3145d4 100644 --- a/lib/features/login/domain/repository/account_repository.dart +++ b/lib/features/login/domain/repository/account_repository.dart @@ -9,4 +9,6 @@ abstract class AccountRepository { Future deleteCurrentAccount(String hashId); Future> getAllAccount(); + + Future setCurrentAccountActive(PersonalAccount activeAccount); } \ No newline at end of file diff --git a/lib/features/login/domain/state/add_account_id_to_active_account_state.dart b/lib/features/login/domain/state/add_account_id_to_active_account_state.dart new file mode 100644 index 0000000000..1dfde78274 --- /dev/null +++ b/lib/features/login/domain/state/add_account_id_to_active_account_state.dart @@ -0,0 +1,11 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class AddAccountIdToActiveAccountLoading extends LoadingState {} + +class AddAccountIdToActiveAccountSuccess extends UIState {} + +class AddAccountIdToActiveAccountFailure extends FeatureFailure { + + AddAccountIdToActiveAccountFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/login/domain/state/set_current_account_active_state.dart b/lib/features/login/domain/state/set_current_account_active_state.dart new file mode 100644 index 0000000000..dbdc1d7aad --- /dev/null +++ b/lib/features/login/domain/state/set_current_account_active_state.dart @@ -0,0 +1,11 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class SetCurrentAccountActiveLoading extends LoadingState {} + +class SetCurrentAccountActiveSuccess extends UIState {} + +class SetCurrentAccountActiveFailure extends FeatureFailure { + + SetCurrentAccountActiveFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/login/domain/state/update_authentication_account_state.dart b/lib/features/login/domain/state/update_authentication_account_state.dart deleted file mode 100644 index c5d1931edf..0000000000 --- a/lib/features/login/domain/state/update_authentication_account_state.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:core/presentation/state/failure.dart'; -import 'package:core/presentation/state/success.dart'; - -class UpdateAuthenticationAccountLoading extends LoadingState {} - -class UpdateAuthenticationAccountSuccess extends UIState {} - -class UpdateAuthenticationAccountFailure extends FeatureFailure { - - UpdateAuthenticationAccountFailure(dynamic exception) : super(exception: exception); -} \ 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/add_account_id_to_active_account_interactor.dart similarity index 67% rename from lib/features/login/domain/usecases/update_authentication_account_interactor.dart rename to lib/features/login/domain/usecases/add_account_id_to_active_account_interactor.dart index b7e0df51d4..bdcf085ed7 100644 --- a/lib/features/login/domain/usecases/update_authentication_account_interactor.dart +++ b/lib/features/login/domain/usecases/add_account_id_to_active_account_interactor.dart @@ -5,27 +5,27 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:tmail_ui_user/features/login/data/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'; +import 'package:tmail_ui_user/features/login/domain/state/add_account_id_to_active_account_state.dart'; -class UpdateAuthenticationAccountInteractor { +class AddAccountIdToActiveAccountInteractor { final AccountRepository _accountRepository; - UpdateAuthenticationAccountInteractor(this._accountRepository); + AddAccountIdToActiveAccountInteractor(this._accountRepository); Stream> execute(AccountId accountId, String apiUrl, UserName userName) async* { try{ - yield Right(UpdateAuthenticationAccountLoading()); + yield Right(AddAccountIdToActiveAccountLoading()); final currentAccount = await _accountRepository.getCurrentAccount(); await _accountRepository.setCurrentAccount( - currentAccount.updateAccountId( + currentAccount.addAccountId( accountId: accountId, apiUrl: apiUrl, userName: userName ) ); - yield Right(UpdateAuthenticationAccountSuccess()); + yield Right(AddAccountIdToActiveAccountSuccess()); } catch(e) { - yield Left(UpdateAuthenticationAccountFailure(e)); + yield Left(AddAccountIdToActiveAccountFailure(e)); } } } \ No newline at end of file diff --git a/lib/features/login/domain/usecases/set_current_account_active_interactor.dart b/lib/features/login/domain/usecases/set_current_account_active_interactor.dart new file mode 100644 index 0000000000..3c64f0b39b --- /dev/null +++ b/lib/features/login/domain/usecases/set_current_account_active_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:model/account/personal_account.dart'; +import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; +import 'package:tmail_ui_user/features/login/domain/state/set_current_account_active_state.dart'; + +class SetCurrentAccountActiveInteractor { + final AccountRepository _accountRepository; + + SetCurrentAccountActiveInteractor(this._accountRepository); + + Stream> execute(PersonalAccount activeAccount) async* { + try{ + yield Right(SetCurrentAccountActiveLoading()); + await _accountRepository.setCurrentAccountActive(activeAccount); + yield Right(SetCurrentAccountActiveSuccess()); + } catch(e) { + yield Left(SetCurrentAccountActiveFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index 38098c7e1a..60e698c156 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -240,7 +240,11 @@ class LoginController extends ReloadableController { switch(loginFormType.value) { case LoginFormType.dnsLookupForm: case LoginFormType.baseUrlForm: - navigateToTwakeIdPage(); + if (PlatformInfo.isMobile && _isAddAnotherAccount) { + popBack(); + } else { + navigateToTwakeIdPage(); + } break; case LoginFormType.passwordForm: _password = null; diff --git a/lib/features/login/presentation/model/login_navigate_arguments.dart b/lib/features/login/presentation/model/login_navigate_arguments.dart index 0c7026155e..5d2509d601 100644 --- a/lib/features/login/presentation/model/login_navigate_arguments.dart +++ b/lib/features/login/presentation/model/login_navigate_arguments.dart @@ -1,4 +1,5 @@ +import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/account/personal_account.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; import 'package:tmail_ui_user/main/routes/router_arguments.dart'; @@ -7,9 +8,21 @@ class LoginNavigateArguments extends RouterArguments { final LoginNavigateType navigateType; final PersonalAccount? currentAccount; + final Session? sessionCurrentAccount; + final PersonalAccount? nextActiveAccount; - LoginNavigateArguments(this.navigateType, this.currentAccount); + LoginNavigateArguments({ + required this.navigateType, + this.currentAccount, + this.sessionCurrentAccount, + this.nextActiveAccount, + }); @override - List get props => [navigateType, currentAccount]; + List get props => [ + navigateType, + currentAccount, + sessionCurrentAccount, + nextActiveAccount, + ]; } \ No newline at end of file diff --git a/lib/features/login/presentation/model/login_navigate_type.dart b/lib/features/login/presentation/model/login_navigate_type.dart index b29a26df15..e110545742 100644 --- a/lib/features/login/presentation/model/login_navigate_type.dart +++ b/lib/features/login/presentation/model/login_navigate_type.dart @@ -1,5 +1,6 @@ enum LoginNavigateType { signIn, - addAnotherAccount; + addAnotherAccount, + switchActiveAccount; } \ 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 b4ae2544a5..cc83b49f28 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -96,9 +96,11 @@ 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/restore_active_account_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'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart'; import 'package:tmail_ui_user/features/mailto/presentation/model/mailto_arguments.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_vacation_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/update_vacation_state.dart'; @@ -491,6 +493,10 @@ class MailboxDashBoardController extends ReloadableController { _handleMailtoURL(arguments); } else if (arguments is PreviewEmailArguments) { _handleOpenEmailAction(arguments); + } else if (arguments is SwitchActiveAccountArguments) { + _handleSwitchActiveAccountAction(arguments); + } else if (arguments is RestoreActiveAccountArguments) { + _handleRestorePreviousActiveAccountAction(arguments); } else { dispatchRoute(DashboardRoutes.thread); reload(); @@ -521,7 +527,7 @@ class MailboxDashBoardController extends ReloadableController { void _handleSession(Session session) { log('MailboxDashBoardController::_handleSession:'); - _setUpComponentsFromSession(session); + _setUpComponentsFromSession(session, saveSession: true); if (PlatformInfo.isMobile && !_notificationManager.isNotificationClickedOnTerminate) { _handleClickLocalNotificationOnTerminated(); @@ -530,7 +536,7 @@ class MailboxDashBoardController extends ReloadableController { } } - void _setUpComponentsFromSession(Session session) { + void _setUpComponentsFromSession(Session session, {bool saveSession = false}) { sessionCurrent = session; accountId.value = sessionCurrent!.personalAccount.accountId; userProfile.value = UserProfile(sessionCurrent!.username.value); @@ -545,11 +551,14 @@ class MailboxDashBoardController extends ReloadableController { if (PlatformInfo.isMobile) { getAllSendingEmails(); - _storeSessionAction( - sessionCurrent!, - accountId.value!, - sessionCurrent!.username - ); + + if (saveSession) { + _storeSessionAction( + sessionCurrent!, + accountId.value!, + sessionCurrent!.username + ); + } } } @@ -1311,7 +1320,7 @@ class MailboxDashBoardController extends ReloadableController { void handleReloaded(Session session) { log('MailboxDashBoardController::handleReloaded():'); _getRouteParameters(); - _setUpComponentsFromSession(session); + _setUpComponentsFromSession(session, saveSession: true); if (PlatformInfo.isWeb) { _handleComposerCache(); } @@ -2504,7 +2513,7 @@ class MailboxDashBoardController extends ReloadableController { leadingSVGIcon: imagePaths.icRecoverDeletedMessages, leadingSVGIconColor: Colors.white, backgroundColor: AppColor.primaryColor, - textColor: Colors.white, + textColor: Colors.white, ); } } @@ -2516,7 +2525,7 @@ class MailboxDashBoardController extends ReloadableController { if (currentAccountId != null && currentSession != null) { final arguments = EmailRecoveryArguments(currentAccountId, currentSession); - final result = PlatformInfo.isWeb + final result = PlatformInfo.isWeb ? await DialogRouter.pushGeneralDialog( routeName: AppRoutes.emailRecovery, arguments: arguments, @@ -2539,6 +2548,40 @@ class MailboxDashBoardController extends ReloadableController { isRecoveringDeletedMessage.value = true; } + void _handleSwitchActiveAccountAction(SwitchActiveAccountArguments arguments) { + log('MailboxDashBoardController::_handleSwitchActiveAccountAction:arguments: $arguments'); + dispatchRoute(DashboardRoutes.waiting); + + setCurrentAccountActive(arguments.nextActiveAccount!); + + dynamicUrlInterceptors.changeBaseUrl(arguments.nextActiveAccount!.apiUrl); + + _setUpComponentsFromSession(arguments.session); + + dispatchRoute(DashboardRoutes.thread); + } + + void _handleRestorePreviousActiveAccountAction(RestoreActiveAccountArguments arguments) { + log('MailboxDashBoardController::_handleRestorePreviousActiveAccountAction:arguments: $arguments'); + + if (currentContext != null && currentOverlayContext != null) { + appToast.showToastErrorMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).toastMessageFailureWhenSwitchActiveAccount); + } + + dispatchRoute(DashboardRoutes.waiting); + + setCurrentAccountActive(arguments.currentAccount); + + dynamicUrlInterceptors.changeBaseUrl(arguments.currentAccount.apiUrl); + + _setUpComponentsFromSession(arguments.session); + + dispatchRoute(DashboardRoutes.thread); + + } + @override void onClose() { _emailReceiveManager.closeEmailReceiveManagerStream(); diff --git a/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart new file mode 100644 index 0000000000..0ea75a2aa4 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart @@ -0,0 +1,21 @@ + +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 RestoreActiveAccountArguments extends RouterArguments { + + final PersonalAccount currentAccount; + final Session session; + + RestoreActiveAccountArguments({ + required this.currentAccount, + required this.session, + }); + + @override + List get props => [ + currentAccount, + session, + ]; +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart new file mode 100644 index 0000000000..94e18673f5 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart @@ -0,0 +1,24 @@ + +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 SwitchActiveAccountArguments extends RouterArguments { + + final Session session; + final PersonalAccount? currentAccount; + final PersonalAccount? nextActiveAccount; + + SwitchActiveAccountArguments({ + required this.session, + this.currentAccount, + this.nextActiveAccount, + }); + + @override + List get props => [ + session, + currentAccount, + nextActiveAccount, + ]; +} \ No newline at end of file diff --git a/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart b/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart index 74e3c37c09..7c05d6bc30 100644 --- a/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart +++ b/lib/features/starting_page/presentation/twake_id/twake_id_controller.dart @@ -39,6 +39,10 @@ class TwakeIdController extends GetxController { } } + void backToHomeView() { + popAndPush(AppRoutes.home); + } + bool get isAddAnotherAccount => navigateArguments.value != null && navigateArguments.value?.navigateType == LoginNavigateType.addAnotherAccount; } \ No newline at end of file diff --git a/lib/features/starting_page/presentation/twake_id/twake_id_view.dart b/lib/features/starting_page/presentation/twake_id/twake_id_view.dart index 20ec6316dd..80d675b60d 100644 --- a/lib/features/starting_page/presentation/twake_id/twake_id_view.dart +++ b/lib/features/starting_page/presentation/twake_id/twake_id_view.dart @@ -4,7 +4,6 @@ import 'package:get/get.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:tmail_ui_user/features/starting_page/presentation/twake_id/twake_id_controller.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class TwakeIdView extends GetWidget { @@ -29,7 +28,7 @@ class TwakeIdView extends GetWidget { backgroundColor: Colors.transparent, margin: const EdgeInsetsDirectional.only(start: 8), iconColor: Colors.black, - onTapActionCallback: popBack) + onTapActionCallback: controller.backToHomeView) : null, ); }); diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index b2ccd6f931..cf93a8a53a 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1246,12 +1246,28 @@ class ThreadController extends BaseController with EmailActionController { mailboxDashBoardController.clearDashBoardAction(); } - void addAnotherAccount(PersonalAccount? currentAccount) { - push( + void addAnotherAccount(PersonalAccount? currentAccount) async { + final result = await popAndPush( AppRoutes.twakeId, arguments: LoginNavigateArguments( - LoginNavigateType.addAnotherAccount, - currentAccount + navigateType: LoginNavigateType.addAnotherAccount, + currentAccount: currentAccount + ) + ); + log('ThreadController::addAnotherAccount:result: $result'); + } + + void switchActiveAccount( + PersonalAccount currentActiveAccount, + PersonalAccount nextActiveAccount + ) async { + await popAndPush( + AppRoutes.home, + arguments: LoginNavigateArguments( + navigateType: LoginNavigateType.switchActiveAccount, + currentAccount: currentActiveAccount, + sessionCurrentAccount: mailboxDashBoardController.sessionCurrent, + nextActiveAccount: nextActiveAccount, ) ); } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index ea8158d240..179eb3a57b 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -106,7 +106,8 @@ class ThreadView extends GetWidget await controller.authenticatedAccountManager.showAccountsBottomSheetModal( context: context, onGoToManageAccount: controller.mailboxDashBoardController.goToSettings, - onAddAnotherAccountAction: controller.addAnotherAccount + onAddAnotherAccountAction: controller.addAnotherAccount, + onSwitchActiveAccountAction: controller.switchActiveAccount ); }, ); diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c7f3f932bc..5cedfec9bf 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-01-07T14:01:28.226469", + "@@last_modified": "2024-01-08T14:23:35.609263", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3671,5 +3671,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "toastMessageFailureWhenSwitchActiveAccount": "Switch active account failure", + "@toastMessageFailureWhenSwitchActiveAccount": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/bindings/credential/credential_bindings.dart b/lib/main/bindings/credential/credential_bindings.dart index c963c56e4d..494a2ead48 100644 --- a/lib/main/bindings/credential/credential_bindings.dart +++ b/lib/main/bindings/credential/credential_bindings.dart @@ -15,13 +15,14 @@ import 'package:tmail_ui_user/features/login/data/repository/authentication_repo import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_repository.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/add_account_id_to_active_account_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_all_authenticated_account_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/logout_current_account_basic_auth_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_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/set_current_account_active_interactor.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; @@ -45,7 +46,8 @@ class CredentialBindings extends InteractorsBindings { Get.find(), Get.find() )); - Get.put(UpdateAuthenticationAccountInteractor(Get.find())); + Get.put(AddAccountIdToActiveAccountInteractor(Get.find())); + Get.put(SetCurrentAccountActiveInteractor(Get.find())); } @override diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 05c8d19930..643fd2d9ed 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3828,4 +3828,11 @@ class AppLocalizations { name: 'addAnotherAccount', ); } + + String get toastMessageFailureWhenSwitchActiveAccount { + return Intl.message( + 'Switch active account failure', + name: 'toastMessageFailureWhenSwitchActiveAccount', + ); + } } \ No newline at end of file diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart index c8bd6f1a17..bd5d32af2a 100644 --- a/lib/main/utils/authenticated_account_manager.dart +++ b/lib/main/utils/authenticated_account_manager.dart @@ -18,6 +18,9 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; typedef OnAddAnotherAccountAction = Function(PersonalAccount? currentAccount); +typedef OnSwitchActiveAccountAction = Function( + PersonalAccount currentActiveAccount, + PersonalAccount nextActiveAccount); class AuthenticatedAccountManager { @@ -78,9 +81,10 @@ class AuthenticatedAccountManager { required BuildContext context, VoidCallback? onGoToManageAccount, OnAddAnotherAccountAction? onAddAnotherAccountAction, + OnSwitchActiveAccountAction? onSwitchActiveAccountAction, }) async { final listPresentationAccount = await _getAllTwakeMailPresentationAccount(); - final activeAccount = listPresentationAccount + final currentActiveAccount = listPresentationAccount .firstWhereOrNull((presentationAccount) => presentationAccount.isActive); if (context.mounted) { @@ -119,9 +123,15 @@ class AuthenticatedAccountManager { titleAccountSettingsStyle: Theme.of(context).textTheme.labelLarge!.copyWith( color: LinagoraSysColors.material().primary, ), - onAddAnotherAccount: () => onAddAnotherAccountAction?.call(activeAccount?.personalAccount), + onAddAnotherAccount: () => onAddAnotherAccountAction?.call(currentActiveAccount?.personalAccount), onGoToAccountSettings: () => onGoToManageAccount?.call(), - onSetAccountAsActive: (presentationAccount) {}, + onSetAccountAsActive: (presentationAccount) { + if (presentationAccount is TwakeMailPresentationAccount) { + onSwitchActiveAccountAction?.call( + currentActiveAccount!.personalAccount, + presentationAccount.personalAccount); + } + }, ); } } From 046c8265ba40635dc7845175adf7a20da003637c Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 9 Jan 2024 15:12:57 +0700 Subject: [PATCH 07/17] TF-2431 Update correct format TupleKey(AccountId|UserName|..) to store data to hive database Signed-off-by: dab246 --- CHANGELOG.md | 2 +- ...0027-use-tuplekey-store-data-to-hive-database.md} | 9 +++++---- lib/features/caching/caching_manager.dart | 6 +++--- lib/features/caching/utils/cache_utils.dart | 4 ++-- .../data/extensions/list_mailbox_extension.dart | 2 +- .../data/extensions/list_mailbox_id_extension.dart | 2 +- .../mailbox/data/local/state_cache_manager.dart | 4 ++-- lib/features/mailbox/data/model/state_type.dart | 2 +- .../manager/new_email_cache_manager.dart | 6 +++--- .../manager/opened_email_cache_manager.dart | 6 +++--- .../manager/sending_email_cache_manager.dart | 12 ++++++------ .../data/local/fcm_cache_manager.dart | 6 +++--- .../thread/data/extensions/list_email_extension.dart | 2 +- .../data/extensions/list_email_id_extension.dart | 2 +- .../thread/data/local/email_cache_manager.dart | 8 ++++---- .../thread/presentation/thread_controller.dart | 2 +- 16 files changed, 38 insertions(+), 37 deletions(-) rename docs/adr/{0027-use-tuplekey-store-data-cache.md => 0027-use-tuplekey-store-data-to-hive-database.md} (56%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b16780217..b4863e5301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -279,7 +279,7 @@ - \#1792 Add option 'Empty Trash' in folder menu ### Changed -- \#1755 Use TupleKey('ObjectId\|AccountId\|UserName') store data to cache +- \#1755 Use TupleKey('AccountId\|UserName\|ObjectId') store data to cache ### Fixed - \#1385 Fix \[Email rule\] Icon edit and delete might be seen as disable diff --git a/docs/adr/0027-use-tuplekey-store-data-cache.md b/docs/adr/0027-use-tuplekey-store-data-to-hive-database.md similarity index 56% rename from docs/adr/0027-use-tuplekey-store-data-cache.md rename to docs/adr/0027-use-tuplekey-store-data-to-hive-database.md index 86924533ff..1170d5b4af 100644 --- a/docs/adr/0027-use-tuplekey-store-data-cache.md +++ b/docs/adr/0027-use-tuplekey-store-data-to-hive-database.md @@ -1,4 +1,4 @@ -# 27. Use TupleKey store data cache +# 27. Use TupleKey store data to hive database Date: 2023-04-27 @@ -9,13 +9,14 @@ Accepted ## Context - Multiple accounts login at the same time in the same browser. The accounts will use the same database (`IndexDatabase`). +- To support multiple accounts ## Decision -- Use unique parameters (`AccountId`, `UserName`, `ObjectId(MailboxId/EmailId/StateType`) to form a unique `key` for storage (called `TupleKey`). -- TupleKey has the format: `ObjectId | AccountId | User`; +- Use unique parameters (`AccountId`, `UserName`, `ObjectId(MailboxId/EmailId/StateType/...)`) to form a unique `key` for storage (called `TupleKey`). +- TupleKey has the format: `AccountId | UserName | [ObjectId]`; - `HiveDatabase` includes many `Box`. Each box is a `Map` with `key=TupleKey`. ## Consequences -- The correct `mailbox` and `email` lists are obtained for each account +- Each account will manage its own data storage boxes diff --git a/lib/features/caching/caching_manager.dart b/lib/features/caching/caching_manager.dart index ffe3b306c9..a3a71a1255 100644 --- a/lib/features/caching/caching_manager.dart +++ b/lib/features/caching/caching_manager.dart @@ -2,7 +2,7 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/file_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:tmail_ui_user/features/caching/clients/account_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/fcm_cache_client.dart'; @@ -97,9 +97,9 @@ class CachingManager { ], eagerError: true); } - Future clearEmailCacheAndStateCacheByTupleKey(AccountId accountId, Session session) { + Future clearEmailCacheAndStateCacheByTupleKey(AccountId accountId, UserName userName) { return Future.wait([ - _stateCacheClient.deleteItem(StateType.email.getTupleKeyStored(accountId, session.username)), + _stateCacheClient.deleteItem(StateType.email.getTupleKeyStored(accountId, userName)), _emailCacheClient.clearAllData(), ], eagerError: true); } diff --git a/lib/features/caching/utils/cache_utils.dart b/lib/features/caching/utils/cache_utils.dart index 954395754a..8000a51a16 100644 --- a/lib/features/caching/utils/cache_utils.dart +++ b/lib/features/caching/utils/cache_utils.dart @@ -12,14 +12,14 @@ class TupleKey { TupleKey( String key1, + String key2, [ - String? key2, String? key3, String? key4, ] ) : parts = [ key1, - if (key2 != null) key2, + key2, if (key3 != null) key3, if (key4 != null) key4, ]; diff --git a/lib/features/mailbox/data/extensions/list_mailbox_extension.dart b/lib/features/mailbox/data/extensions/list_mailbox_extension.dart index 5ddabf3b4c..031af8eeaf 100644 --- a/lib/features/mailbox/data/extensions/list_mailbox_extension.dart +++ b/lib/features/mailbox/data/extensions/list_mailbox_extension.dart @@ -12,7 +12,7 @@ extension ListMailboxExtension on List { Map toMapCache(AccountId accountId, UserName userName) { return { for (var mailbox in this) - TupleKey(mailbox.id!.asString, accountId.asString, userName.value).encodeKey : mailbox.toMailboxCache() + TupleKey(accountId.asString, userName.value, mailbox.id!.asString).encodeKey : mailbox.toMailboxCache() }; } } \ No newline at end of file diff --git a/lib/features/mailbox/data/extensions/list_mailbox_id_extension.dart b/lib/features/mailbox/data/extensions/list_mailbox_id_extension.dart index 4f3e083c12..4164f4a16a 100644 --- a/lib/features/mailbox/data/extensions/list_mailbox_id_extension.dart +++ b/lib/features/mailbox/data/extensions/list_mailbox_id_extension.dart @@ -8,5 +8,5 @@ import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; extension ListMailboxIdExtension on List { List toCacheKeyList(AccountId accountId, UserName userName) => - map((id) => TupleKey(id.asString, accountId.asString, userName.value).encodeKey).toList(); + map((id) => TupleKey(accountId.asString, userName.value, id.asString).encodeKey).toList(); } \ No newline at end of file diff --git a/lib/features/mailbox/data/local/state_cache_manager.dart b/lib/features/mailbox/data/local/state_cache_manager.dart index 22066f4d58..dfe7f32697 100644 --- a/lib/features/mailbox/data/local/state_cache_manager.dart +++ b/lib/features/mailbox/data/local/state_cache_manager.dart @@ -15,14 +15,14 @@ class StateCacheManager { StateCacheManager(this._stateCacheClient); Future getState(AccountId accountId, UserName userName, StateType stateType) async { - final stateKey = TupleKey(stateType.name, accountId.asString, userName.value).encodeKey; + final stateKey = TupleKey(accountId.asString, userName.value, stateType.name).encodeKey; final stateCache = await _stateCacheClient.getItem(stateKey); return stateCache?.toState(); } Future saveState(AccountId accountId, UserName userName, StateCache stateCache) async { final stateCacheExist = await _stateCacheClient.isExistTable(); - final stateKey = TupleKey(stateCache.type.name, accountId.asString, userName.value).encodeKey; + final stateKey = TupleKey(accountId.asString, userName.value, stateCache.type.name).encodeKey; if (stateCacheExist) { return await _stateCacheClient.updateItem(stateKey, stateCache); } else { diff --git a/lib/features/mailbox/data/model/state_type.dart b/lib/features/mailbox/data/model/state_type.dart index b7786c0d22..a96426a154 100644 --- a/lib/features/mailbox/data/model/state_type.dart +++ b/lib/features/mailbox/data/model/state_type.dart @@ -18,6 +18,6 @@ enum StateType { email; String getTupleKeyStored(AccountId accountId, UserName userName) { - return TupleKey(name, accountId.asString, userName.value).encodeKey; + return TupleKey(accountId.asString, userName.value, name).encodeKey; } } \ No newline at end of file diff --git a/lib/features/offline_mode/manager/new_email_cache_manager.dart b/lib/features/offline_mode/manager/new_email_cache_manager.dart index 83bf38b535..14cf96b0dc 100644 --- a/lib/features/offline_mode/manager/new_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/new_email_cache_manager.dart @@ -46,7 +46,7 @@ class NewEmailCacheManager { UserName userName, DetailedEmailHiveCache detailedEmailCache ) { - final keyCache = TupleKey(detailedEmailCache.emailId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, detailedEmailCache.emailId).encodeKey; return _cacheClient.insertItem(keyCache, detailedEmailCache); } @@ -55,7 +55,7 @@ class NewEmailCacheManager { UserName userName, String emailId ) { - final keyCache = TupleKey(emailId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, emailId).encodeKey; return _cacheClient.deleteItem(keyCache); } @@ -74,7 +74,7 @@ class NewEmailCacheManager { UserName userName, EmailId emailId ) async { - final keyCache = TupleKey(emailId.asString, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, emailId.asString).encodeKey; final detailedEmailCache = await _cacheClient.getItem(keyCache, needToReopen: true); if (detailedEmailCache != null) { return detailedEmailCache; diff --git a/lib/features/offline_mode/manager/opened_email_cache_manager.dart b/lib/features/offline_mode/manager/opened_email_cache_manager.dart index b6f791ab11..da50a1f69a 100644 --- a/lib/features/offline_mode/manager/opened_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/opened_email_cache_manager.dart @@ -24,7 +24,7 @@ class OpenedEmailCacheManager { UserName userName, DetailedEmailHiveCache detailedEmailCache ) { - final keyCache = TupleKey(detailedEmailCache.emailId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, detailedEmailCache.emailId).encodeKey; log('OpenedEmailCacheManager::insertDetailedEmail(): $keyCache'); return _cacheClient.insertItem(keyCache, detailedEmailCache); } @@ -34,7 +34,7 @@ class OpenedEmailCacheManager { UserName userName, String emailId ) { - final keyCache = TupleKey(emailId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, emailId).encodeKey; log('OpenedEmailCacheManager::removeDetailedEmail(): $keyCache'); return _cacheClient.deleteItem(keyCache); } @@ -72,7 +72,7 @@ class OpenedEmailCacheManager { UserName userName, EmailId emailId ) async { - final keyCache = TupleKey(emailId.asString, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, emailId.asString).encodeKey; final detailedEmailCache = await _cacheClient.getItem(keyCache, needToReopen: true); if (detailedEmailCache != null) { return detailedEmailCache; diff --git a/lib/features/offline_mode/manager/sending_email_cache_manager.dart b/lib/features/offline_mode/manager/sending_email_cache_manager.dart index 34c6fecb51..314bb06d04 100644 --- a/lib/features/offline_mode/manager/sending_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/sending_email_cache_manager.dart @@ -19,7 +19,7 @@ class SendingEmailCacheManager { UserName userName, SendingEmailHiveCache sendingEmailHiveCache ) async { - final keyCache = TupleKey(sendingEmailHiveCache.sendingId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, sendingEmailHiveCache.sendingId).encodeKey; await _hiveCacheClient.insertItem(keyCache, sendingEmailHiveCache); final newSendingEmailHiveCache = await _hiveCacheClient.getItem(keyCache); if (newSendingEmailHiveCache != null) { @@ -36,7 +36,7 @@ class SendingEmailCacheManager { } Future deleteSendingEmail(AccountId accountId, UserName userName, String sendingId) async { - final keyCache = TupleKey(sendingId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, sendingId).encodeKey; await _hiveCacheClient.deleteItem(keyCache); final storedSendingEmail = await _hiveCacheClient.getItem(keyCache); if (storedSendingEmail != null) { @@ -57,7 +57,7 @@ class SendingEmailCacheManager { UserName userName, SendingEmailHiveCache sendingEmailHiveCache ) async { - final keyCache = TupleKey(sendingEmailHiveCache.sendingId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, sendingEmailHiveCache.sendingId).encodeKey; await _hiveCacheClient.updateItem(keyCache, sendingEmailHiveCache); final newSendingEmailHiveCache = await _hiveCacheClient.getItem(keyCache); if (newSendingEmailHiveCache != null) { @@ -74,7 +74,7 @@ class SendingEmailCacheManager { ) async { final mapSendingEmailCache = { for (var sendingEmailCache in listSendingEmailHiveCache) - TupleKey(sendingEmailCache.sendingId, accountId.asString, userName.value).encodeKey: sendingEmailCache + TupleKey(accountId.asString, userName.value, sendingEmailCache.sendingId).encodeKey: sendingEmailCache }; await _hiveCacheClient.updateMultipleItem(mapSendingEmailCache); final newListSendingEmailCache = await _hiveCacheClient.getValuesByListKey(mapSendingEmailCache.keys.toList()); @@ -82,7 +82,7 @@ class SendingEmailCacheManager { } Future deleteMultipleSendingEmail(AccountId accountId, UserName userName, List sendingIds) async { - final listTupleKey = sendingIds.map((sendingId) => TupleKey(sendingId, accountId.asString, userName.value).encodeKey).toList(); + final listTupleKey = sendingIds.map((sendingId) => TupleKey(accountId.asString, userName.value, sendingId).encodeKey).toList(); await _hiveCacheClient.deleteMultipleItem(listTupleKey); final newListSendingEmailCache = await _hiveCacheClient.getValuesByListKey(listTupleKey); if (newListSendingEmailCache.isNotEmpty) { @@ -91,7 +91,7 @@ class SendingEmailCacheManager { } Future getStoredSendingEmail(AccountId accountId, UserName userName, String sendingId) async { - final keyCache = TupleKey(sendingId, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, sendingId).encodeKey; final storedSendingEmail = await _hiveCacheClient.getItem(keyCache); if (storedSendingEmail != null) { return storedSendingEmail; diff --git a/lib/features/push_notification/data/local/fcm_cache_manager.dart b/lib/features/push_notification/data/local/fcm_cache_manager.dart index 7656b94cae..797a78b724 100644 --- a/lib/features/push_notification/data/local/fcm_cache_manager.dart +++ b/lib/features/push_notification/data/local/fcm_cache_manager.dart @@ -16,12 +16,12 @@ class FCMCacheManager { FCMCacheManager(this._fcmCacheClient,this._firebaseRegistrationCacheClient); Future storeStateToRefresh(AccountId accountId, UserName userName, TypeName typeName, jmap.State newState) { - final stateKeyCache = TupleKey(typeName.value, accountId.asString, userName.value).encodeKey; + final stateKeyCache = TupleKey(accountId.asString, userName.value, typeName.value).encodeKey; return _fcmCacheClient.insertItem(stateKeyCache, newState.value); } Future getStateToRefresh(AccountId accountId, UserName userName, TypeName typeName) async { - final stateKeyCache = TupleKey(typeName.value, accountId.asString, userName.value).encodeKey; + final stateKeyCache = TupleKey(accountId.asString, userName.value, typeName.value).encodeKey; final stateValue = await _fcmCacheClient.getItem(stateKeyCache); if (stateValue != null) { return jmap.State(stateValue); @@ -35,7 +35,7 @@ class FCMCacheManager { } Future deleteStateToRefresh(AccountId accountId, UserName userName, TypeName typeName) { - final stateKeyCache = TupleKey(typeName.value, accountId.asString, userName.value).encodeKey; + final stateKeyCache = TupleKey(accountId.asString, userName.value, typeName.value).encodeKey; return _fcmCacheClient.deleteItem(stateKeyCache); } diff --git a/lib/features/thread/data/extensions/list_email_extension.dart b/lib/features/thread/data/extensions/list_email_extension.dart index 9640dffee6..d5d05ce812 100644 --- a/lib/features/thread/data/extensions/list_email_extension.dart +++ b/lib/features/thread/data/extensions/list_email_extension.dart @@ -12,7 +12,7 @@ extension ListEmailExtension on List { Map toMapCache(AccountId accountId, UserName userName) { return { for (var email in this) - TupleKey(email.id!.asString, accountId.asString, userName.value).encodeKey : email.toEmailCache() + TupleKey(accountId.asString, userName.value, email.id!.asString).encodeKey : email.toEmailCache() }; } } \ No newline at end of file diff --git a/lib/features/thread/data/extensions/list_email_id_extension.dart b/lib/features/thread/data/extensions/list_email_id_extension.dart index f164824263..2bb6445a2e 100644 --- a/lib/features/thread/data/extensions/list_email_id_extension.dart +++ b/lib/features/thread/data/extensions/list_email_id_extension.dart @@ -8,5 +8,5 @@ import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; extension ListEmailIdExtension on List { List toCacheKeyList(AccountId accountId, UserName userName) => - map((id) => TupleKey(id.asString, accountId.asString, userName.value).encodeKey).toList(); + map((id) => TupleKey(accountId.asString, userName.value, id.asString).encodeKey).toList(); } \ No newline at end of file diff --git a/lib/features/thread/data/local/email_cache_manager.dart b/lib/features/thread/data/local/email_cache_manager.dart index 0e37fe9d51..cad8cc1372 100644 --- a/lib/features/thread/data/local/email_cache_manager.dart +++ b/lib/features/thread/data/local/email_cache_manager.dart @@ -1,9 +1,10 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; -import 'package:model/model.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/model.dart'; import 'package:tmail_ui_user/features/caching/clients/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/cleanup/domain/model/email_cleanup_rule.dart'; @@ -13,7 +14,6 @@ import 'package:tmail_ui_user/features/thread/data/extensions/email_extension.da import 'package:tmail_ui_user/features/thread/data/extensions/list_email_cache_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/list_email_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/list_email_id_extension.dart'; -import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; @@ -94,12 +94,12 @@ class EmailCacheManager { } Future storeEmail(AccountId accountId, UserName userName, EmailCache emailCache) { - final keyCache = TupleKey(emailCache.id, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, emailCache.id).encodeKey; return _emailCacheClient.insertItem(keyCache, emailCache); } Future getStoredEmail(AccountId accountId, UserName userName, EmailId emailId) async { - final keyCache = TupleKey(emailId.asString, accountId.asString, userName.value).encodeKey; + final keyCache = TupleKey(accountId.asString, userName.value, emailId.asString).encodeKey; final emailCache = await _emailCacheClient.getItem(keyCache, needToReopen: true); if (emailCache != null) { return emailCache; diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index cf93a8a53a..7522d9ccdf 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -371,7 +371,7 @@ class ThreadController extends BaseController with EmailActionController { logError('ThreadController::_handleErrorGetAllOrRefreshChangesEmail():Error: $error'); if (error is CannotCalculateChangesMethodResponseException) { if (_accountId != null && _session != null) { - await cachingManager.clearEmailCacheAndStateCacheByTupleKey(_accountId!, _session!); + await cachingManager.clearEmailCacheAndStateCacheByTupleKey(_accountId!, _session!.username); } else { await cachingManager.clearEmailCacheAndAllStateCache(); } From 5b7f8a039aa047fd27528f00cd50b2b9cb113a4e Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 9 Jan 2024 19:09:01 +0700 Subject: [PATCH 08/17] TF-2431 Handle logout current active account Signed-off-by: dab246 --- lib/features/base/base_controller.dart | 158 +++++++++++++----- .../reloadable/reloadable_controller.dart | 16 +- .../upgrade_hive_database_steps.dart | 17 -- lib/features/caching/caching_manager.dart | 68 ++++++-- .../caching/config/hive_cache_client.dart | 34 +++- .../caching/config/hive_cache_config.dart | 2 - .../email_hive_cache_datasource_impl.dart | 6 +- .../extensions/detailed_email_extension.dart | 14 +- .../model/login_navigate_type.dart | 3 +- .../data/local/mailbox_cache_manager.dart | 18 +- .../manager/new_email_cache_manager.dart | 3 +- .../manager/opened_email_cache_manager.dart | 3 +- .../manager/sending_email_cache_manager.dart | 15 +- .../destroy_firebase_registration_state.dart | 17 +- ...move_firebase_registration_interactor.dart | 21 ++- .../data/local/email_cache_manager.dart | 3 +- .../presentation/thread_controller.dart | 2 - lib/l10n/intl_messages.arb | 12 +- lib/main/localizations/app_localizations.dart | 8 + .../utils/authenticated_account_manager.dart | 24 ++- 20 files changed, 312 insertions(+), 132 deletions(-) delete mode 100644 lib/features/base/upgradeable/upgrade_hive_database_steps.dart diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 246b707b73..2c71bad12b 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -34,6 +34,8 @@ import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_acco import 'package:tmail_ui_user/features/login/domain/usecases/set_current_account_active_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/contact_autocomplete_bindings.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/tmail_autocomplete_bindings.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; @@ -42,7 +44,6 @@ import 'package:tmail_ui_user/features/manage_account/presentation/forward/bindi import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_interactors_bindings.dart'; import 'package:tmail_ui_user/features/push_notification/domain/exceptions/fcm_exception.dart'; import 'package:tmail_ui_user/features/push_notification/domain/state/destroy_firebase_registration_state.dart'; -import 'package:tmail_ui_user/features/push_notification/domain/state/get_stored_firebase_registration_state.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/remove_firebase_registration_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/config/fcm_configuration.dart'; @@ -60,6 +61,7 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/app_store.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; +import 'package:tmail_ui_user/main/utils/authenticated_account_manager.dart'; import 'package:uuid/uuid.dart'; abstract class BaseController extends GetxController @@ -78,6 +80,7 @@ abstract class BaseController extends GetxController final Uuid uuid = Get.find(); final AppStore appStore = Get.find(); final SetCurrentAccountActiveInteractor _setCurrentAccountActiveInteractor = Get.find(); + final AuthenticatedAccountManager authenticatedAccountManager = Get.find(); final _fcmReceiver = FcmReceiver.instance; bool _isFcmEnabled = false; @@ -176,7 +179,7 @@ abstract class BaseController extends GetxController return; } if (exception is BadCredentialsException || exception is ConnectionError) { - clearDataAndGoToLoginPage(); + _handleBadCredentials(); } } @@ -185,19 +188,18 @@ abstract class BaseController extends GetxController if (failure is LogoutCurrentAccountOidcFailure || failure is LogoutCurrentAccountBasicAuthFailure || failure is LogoutCurrentAccountFailure) { - await _handleLogoutCurrentAccountFailure(failure); - } else if (failure is GetStoredFirebaseRegistrationFailure || - failure is DestroyFirebaseRegistrationFailure) { - await clearDataAndGoToLoginPage(); + _handleLogoutCurrentAccountFailure(failure); + } else if (failure is DestroyFirebaseRegistrationFailure) { + _handleDestroyFirebaseRegistrationFailure(failure); } } void handleSuccessViewState(Success success) async { log('BaseController::handleSuccessViewState(): ${success.runtimeType}'); if (success is LogoutCurrentAccountOidcSuccess || success is LogoutCurrentAccountBasicAuthSuccess) { - await _handleLogoutCurrentAccountSuccess(success); + _handleLogoutCurrentAccountSuccess(success); } else if (success is DestroyFirebaseRegistrationSuccess) { - await clearDataAndGoToLoginPage(); + _handleDestroyFirebaseRegistrationSuccess(success); } } @@ -277,20 +279,9 @@ abstract class BaseController extends GetxController bool _isFcmActivated(Session session, AccountId accountId) => FirebaseCapability.fcmIdentifier.isSupported(session, accountId) && AppConfig.fcmAvailable; - void goToLogin({LoginArguments? arguments}) { - if (PlatformInfo.isMobile) { - navigateToTwakeIdPage(); - } else { - navigateToLoginPage(arguments: arguments); - } - } - void removeAllPageAndGoToLogin({LoginArguments? arguments}) { - if (PlatformInfo.isMobile) { - pushAndPopAll(AppRoutes.twakeId); - } else { - navigateToLoginPage(arguments: arguments); - } + void removeAllRouteAndNavigateToTwakeIdPage() { + pushAndPopAll(AppRoutes.twakeId); } void navigateToTwakeIdPage() { @@ -314,39 +305,45 @@ abstract class BaseController extends GetxController void _removeFirebaseRegistration(PersonalAccount deletedAccount) async { _removeFirebaseRegistrationInteractor = getBinding(); - if (_removeFirebaseRegistrationInteractor != null && - deletedAccount.accountId != null && - deletedAccount.userName != null) { - consumeState(_removeFirebaseRegistrationInteractor!.execute( - deletedAccount.accountId!, - deletedAccount.userName!)); + if (_removeFirebaseRegistrationInteractor != null) { + consumeState(_removeFirebaseRegistrationInteractor!.execute(deletedAccount)); } else { - await clearDataAndGoToLoginPage(); + if (PlatformInfo.isMobile) { + await clearDataByAccount(deletedAccount); + _handleNavigationRouteAfterLogoutCurrentAccountSuccess(); + } else { + await clearAllDataAndBackToLogin(); + } } } - Future clearDataAndGoToLoginPage() async { - log('BaseController::clearDataAndGoToLoginPage:'); + Future clearAllDataAndBackToLogin() async { + log('BaseController::clearAllDataAndBackToLogin:'); await clearAllData(); - removeAllPageAndGoToLogin(arguments: LoginArguments( - PlatformInfo.isWeb - ? LoginFormType.none - : LoginFormType.dnsLookupForm - )); + if (PlatformInfo.isMobile) { + removeAllRouteAndNavigateToTwakeIdPage(); + } else { + navigateToLoginPage(arguments: LoginArguments(LoginFormType.none)); + } } Future clearAllData() async { + log('BaseController::clearAllData:'); try { + authorizationInterceptors.clear(); + authorizationIsolateInterceptors.clear(); + await Future.wait([ cachingManager.clearAll(), languageCacheManager.removeLanguage(), ]); + + await cachingManager.closeHive(); + if (PlatformInfo.isMobile) { - await cachingManager.clearAllFileInStorage(); + await cachingManager.clearAllFolderInStorage(); } - authorizationInterceptors.clear(); - authorizationIsolateInterceptors.clear(); - await cachingManager.closeHive(); + if (_isFcmEnabled) { await _fcmReceiver.deleteFcmToken(); } @@ -355,7 +352,22 @@ abstract class BaseController extends GetxController } } - Future _handleLogoutCurrentAccountSuccess(Success success) async { + Future clearDataByAccount(PersonalAccount currentAccount) async { + log('BaseController::clearDataByAccount:currentAccount: $currentAccount'); + try { + authorizationInterceptors.clear(); + authorizationIsolateInterceptors.clear(); + + await cachingManager.clearCacheByAccount(currentAccount); + await cachingManager.closeHive(); + + await cachingManager.clearFolderStorageByAccount(currentAccount); + } catch (e, s) { + logError('BaseController::clearAllDataByAccount: Exception: $e | Stack: $s'); + } + } + + void _handleLogoutCurrentAccountSuccess(Success success) async { PersonalAccount? deletedAccount; if (success is LogoutCurrentAccountOidcSuccess) { @@ -364,14 +376,24 @@ abstract class BaseController extends GetxController deletedAccount = success.deletedAccount; } - if (_isFcmEnabled && deletedAccount != null) { + if (deletedAccount == null) { + await clearAllDataAndBackToLogin(); + return; + } + + if (_isFcmEnabled) { _removeFirebaseRegistration(deletedAccount); } else { - await clearDataAndGoToLoginPage(); + if (PlatformInfo.isMobile) { + await clearDataByAccount(deletedAccount); + _handleNavigationRouteAfterLogoutCurrentAccountSuccess(); + } else { + await clearAllDataAndBackToLogin(); + } } } - Future _handleLogoutCurrentAccountFailure(Failure failure) async { + void _handleLogoutCurrentAccountFailure(Failure failure) async { PersonalAccount? deletedAccount; if (failure is LogoutCurrentAccountOidcFailure) { @@ -382,10 +404,20 @@ abstract class BaseController extends GetxController deletedAccount = failure.deletedAccount; } - if (_isFcmEnabled && deletedAccount != null) { + if (deletedAccount == null) { + await clearAllDataAndBackToLogin(); + return; + } + + if (_isFcmEnabled) { _removeFirebaseRegistration(deletedAccount); } else { - await clearDataAndGoToLoginPage(); + if (PlatformInfo.isMobile) { + await clearDataByAccount(deletedAccount); + _handleNavigationRouteAfterLogoutCurrentAccountSuccess(); + } else { + await clearAllDataAndBackToLogin(); + } } } @@ -422,4 +454,40 @@ abstract class BaseController extends GetxController void setCurrentAccountActive(PersonalAccount activeAccount) { consumeState(_setCurrentAccountActiveInteractor.execute(activeAccount)); } + + void _handleBadCredentials() async { + await clearAllDataAndBackToLogin(); + } + + void _handleDestroyFirebaseRegistrationFailure(DestroyFirebaseRegistrationFailure failure) async { + if (PlatformInfo.isMobile) { + await clearDataByAccount(failure.currentAccount); + _handleNavigationRouteAfterLogoutCurrentAccountSuccess(); + } else { + await clearAllDataAndBackToLogin(); + } + } + + void _handleDestroyFirebaseRegistrationSuccess(DestroyFirebaseRegistrationSuccess success) async { + if (PlatformInfo.isMobile) { + await clearDataByAccount(success.currentAccount); + _handleNavigationRouteAfterLogoutCurrentAccountSuccess(); + } else { + await clearAllDataAndBackToLogin(); + } + } + + void _handleNavigationRouteAfterLogoutCurrentAccountSuccess() async { + log('BaseController::_handleNavigationRouteAfterLogoutCurrentAccountSuccess:'); + final listAccounts = await authenticatedAccountManager.getAllPersonalAccount(); + if (listAccounts.isEmpty) { + removeAllRouteAndNavigateToTwakeIdPage(); + } else { + pushAndPopAll( + AppRoutes.home, + arguments: LoginNavigateArguments( + navigateType: LoginNavigateType.selectActiveAccount + )); + } + } } diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index 21bb7d8909..8196c3fe87 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -30,13 +30,11 @@ abstract class ReloadableController extends BaseController { if (failure is GetSessionFailure) { _handleGetSessionFailure(failure.exception); } else if (failure is GetAuthenticatedAccountFailure) { - goToLogin( - arguments: LoginArguments( - PlatformInfo.isMobile - ? LoginFormType.dnsLookupForm - : LoginFormType.none - ) - ); + if (PlatformInfo.isMobile) { + navigateToTwakeIdPage(); + } else { + navigateToLoginPage(arguments: LoginArguments(LoginFormType.none)); + } } } @@ -79,7 +77,7 @@ abstract class ReloadableController extends BaseController { MessageToastUtils.getMessageByException(currentContext!, exception) ?? AppLocalizations.of(currentContext!).unknownError ); } - clearDataAndGoToLoginPage(); + clearAllDataAndBackToLogin(); } void _handleGetSessionSuccess(GetSessionSuccess success) { @@ -95,7 +93,7 @@ abstract class ReloadableController extends BaseController { ); handleReloaded(session); } else { - clearDataAndGoToLoginPage(); + clearAllDataAndBackToLogin(); } } diff --git a/lib/features/base/upgradeable/upgrade_hive_database_steps.dart b/lib/features/base/upgradeable/upgrade_hive_database_steps.dart deleted file mode 100644 index 197120fd13..0000000000 --- a/lib/features/base/upgradeable/upgrade_hive_database_steps.dart +++ /dev/null @@ -1,17 +0,0 @@ - -import 'package:tmail_ui_user/features/base/upgradeable/upgrade_database_steps.dart'; -import 'package:tmail_ui_user/features/caching/caching_manager.dart'; - -class UpgradeHiveDatabaseSteps extends UpgradeDatabaseSteps { - - final CachingManager _cachingManager; - - UpgradeHiveDatabaseSteps(this._cachingManager); - - @override - Future onUpgrade(int oldVersion, int newVersion) async { - if (oldVersion != newVersion) { - await _cachingManager.clearData(); - } - } -} \ No newline at end of file diff --git a/lib/features/caching/caching_manager.dart b/lib/features/caching/caching_manager.dart index a3a71a1255..dae83d2068 100644 --- a/lib/features/caching/caching_manager.dart +++ b/lib/features/caching/caching_manager.dart @@ -3,6 +3,8 @@ import 'package:core/utils/file_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:model/extensions/account_id_extensions.dart'; import 'package:tmail_ui_user/features/caching/clients/account_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/fcm_cache_client.dart'; @@ -15,6 +17,7 @@ import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_clien import 'package:tmail_ui_user/features/caching/clients/session_hive_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/state_cache_client.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/local/local_spam_report_manager.dart'; @@ -79,21 +82,24 @@ class CachingManager { ], eagerError: true); } - Future clearData() async { + Future clearCacheByAccount(PersonalAccount currentAccount) async { + final accountKey = TupleKey( + currentAccount.accountId!.asString, + currentAccount.userName!.value + ).encodeKey; + await Future.wait([ - _stateCacheClient.clearAllData(), - _mailboxCacheClient.clearAllData(), - _emailCacheClient.clearAllData(), - _fcmCacheClient.clearAllData(), - _firebaseRegistrationCacheClient.clearAllData(), - _recentSearchCacheClient.clearAllData(), - _localSpamReportManager.clear(), - if (PlatformInfo.isMobile) - ...[ - _newEmailHiveCacheClient.clearAllData(), - _openedEmailHiveCacheClient.clearAllData(), - _clearSendingEmailCache(), - ] + _sessionHiveCacheClient.clearAllDataContainKey(accountKey), + _mailboxCacheClient.clearAllDataContainKey(accountKey), + _emailCacheClient.clearAllDataContainKey(accountKey), + _stateCacheClient.clearAllDataContainKey(accountKey), + _fcmCacheClient.clearAllDataContainKey(accountKey), + _firebaseRegistrationCacheClient.clearAllDataContainKey(accountKey), + _newEmailHiveCacheClient.clearAllDataContainKey(accountKey), + _openedEmailHiveCacheClient.clearAllDataContainKey(accountKey), + _clearSendingEmailCacheByAccount(currentAccount), + if (PlatformInfo.isIOS) + _keychainSharingManager.delete(accountId: currentAccount.accountId?.asString) ], eagerError: true); } @@ -124,13 +130,29 @@ class CachingManager { return await HiveCacheConfig().closeHive(); } - Future clearAllFileInStorage() async { + Future clearAllFolderInStorage() async { await Future.wait([ _fileUtils.removeFolder(CachingConstants.newEmailsContentFolderName), _fileUtils.removeFolder(CachingConstants.openedEmailContentFolderName), ]); } + Future clearFolderStorageByAccount(PersonalAccount currentAccount) async { + final folderKey = TupleKey( + currentAccount.accountId!.asString, + currentAccount.userName!.value + ).encodeKey; + + await Future.wait([ + _fileUtils.removeFolder( + '${CachingConstants.newEmailsContentFolderName}/$folderKey' + ), + _fileUtils.removeFolder( + '${CachingConstants.openedEmailContentFolderName}/$folderKey' + ), + ]); + } + Future _clearSendingEmailCache() async { final listSendingEmails = await _sendingEmailCacheManager.getAllSendingEmails(); final sendingIds = listSendingEmails.map((sendingEmail) => sendingEmail.sendingId).toSet().toList(); @@ -142,4 +164,20 @@ class CachingManager { await _sendingEmailCacheManager.clearAllSendingEmails(); } } + + Future _clearSendingEmailCacheByAccount(PersonalAccount currentAccount) async { + final listSendingEmails = await _sendingEmailCacheManager.getAllSendingEmailsByAccount( + currentAccount.accountId!, + currentAccount.userName!); + + final sendingIds = listSendingEmails.map((sendingEmail) => sendingEmail.sendingId).toSet().toList(); + if (sendingIds.isNotEmpty) { + await Future.wait( + sendingIds.map(WorkManagerController().cancelByUniqueId), + eagerError: true + ); + + await _sendingEmailCacheManager.clearAllSendingEmailsByAccount(currentAccount); + } + } } diff --git a/lib/features/caching/config/hive_cache_client.dart b/lib/features/caching/config/hive_cache_client.dart index 89ca70241b..3e6c1a04ca 100644 --- a/lib/features/caching/config/hive_cache_client.dart +++ b/lib/features/caching/config/hive_cache_client.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:core/presentation/extensions/map_extensions.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:hive/hive.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; @@ -80,13 +81,15 @@ abstract class HiveCacheClient { }); } - Future> getListByTupleKey(String accountId, String userName) { + Future> getListByNestedKey(String nestedKey) { return Future.sync(() async { final boxItem = encryption ? await openBoxEncryption() : await openBox(); - return boxItem.toMap() - .where((key, value) => _matchedKey(key, accountId, userName)) + final listItem = boxItem.toMap() + .where((key, value) => _matchedNestedKey(key, nestedKey)) .values .toList(); + log('HiveCacheClient::getListByNestedKey:listItem: ${listItem.length}'); + return listItem; }).catchError((error) { throw error; }); @@ -104,12 +107,6 @@ abstract class HiveCacheClient { }); } - bool _matchedKey(String key, String accountId, String userName) { - final keyDecoded = CacheUtils.decodeKey(key); - final tupleKey = TupleKey.fromString(keyDecoded); - return tupleKey.parts.length >= 3 && tupleKey.parts[1] == accountId && tupleKey.parts[2] == userName; - } - Future updateItem(String key, T newObject) { return Future.sync(() async { final boxItem = encryption ? await openBoxEncryption() : await openBox(); @@ -182,6 +179,25 @@ abstract class HiveCacheClient { }); } + Future clearAllDataContainKey(String nestedKey) { + return Future.sync(() async { + final boxItem = encryption ? await openBoxEncryption() : await openBox(); + + final mapItemNotContainNestedKey = boxItem.toMap() + .where((key, value) => !_matchedNestedKey(key, nestedKey)); + log('HiveCacheClient::clearAllDataContainKey:mapItemNotContainNestedKey: ${mapItemNotContainNestedKey.length}'); + return boxItem.putAll(mapItemNotContainNestedKey); + }).catchError((error) { + throw error; + }); + } + + bool _matchedNestedKey(String key, String nestedKey) { + final decodedKey = CacheUtils.decodeKey(key); + final decodedNestedKey = CacheUtils.decodeKey(nestedKey); + return decodedKey.contains(decodedNestedKey); + } + Future closeBox() async { if (Hive.isBoxOpen(tableName)) { await Hive.box(tableName).close(); diff --git a/lib/features/caching/config/hive_cache_config.dart b/lib/features/caching/config/hive_cache_config.dart index d4fdb849b1..fd69c23546 100644 --- a/lib/features/caching/config/hive_cache_config.dart +++ b/lib/features/caching/config/hive_cache_config.dart @@ -6,7 +6,6 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps.dart'; import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v10.dart'; import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; @@ -58,7 +57,6 @@ class HiveCacheConfig { const newVersion = CacheVersion.hiveDBVersion; log('HiveCacheConfig::onUpgradeDatabase():oldVersion: $oldVersion | newVersion: $newVersion'); - await UpgradeHiveDatabaseSteps(cachingManager).onUpgrade(oldVersion, newVersion); await UpgradeHiveDatabaseStepsV7(cachingManager).onUpgrade(oldVersion, newVersion); await UpgradeHiveDatabaseStepsV10(cachingManager).onUpgrade(oldVersion, newVersion); diff --git a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart index fb43275879..8ed2922eda 100644 --- a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart @@ -139,7 +139,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { final fileSaved = await _fileUtils.saveToFile( nameFile: detailedEmail.emailId.asString, content: detailedEmail.htmlEmailContent ?? '', - folderPath: detailedEmail.newEmailFolderPath + folderPath: detailedEmail.getNewEmailFolderPath(accountId, session.username) ); final detailedEmailSaved = detailedEmail.fromEmailContentPath(fileSaved.path); @@ -182,7 +182,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { final fileSaved = await _fileUtils.saveToFile( nameFile: detailedEmail.emailId.asString, content: detailedEmail.htmlEmailContent ?? '', - folderPath: detailedEmail.openedEmailFolderPath + folderPath: detailedEmail.getOpenedEmailFolderPath(accountId, session.username) ); final detailedEmailSaved = detailedEmail.fromEmailContentPath(fileSaved.path); @@ -247,7 +247,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { @override Future> getAllSendingEmails(AccountId accountId, UserName userName) { return Future.sync(() async { - final sendingEmailsCache = await _sendingEmailCacheManager.getAllSendingEmailsByTupleKey(accountId, userName); + final sendingEmailsCache = await _sendingEmailCacheManager.getAllSendingEmailsByAccount(accountId, userName); return sendingEmailsCache.toSendingEmails(); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/email/domain/extensions/detailed_email_extension.dart b/lib/features/email/domain/extensions/detailed_email_extension.dart index c7b9396db9..6b331b9b47 100644 --- a/lib/features/email/domain/extensions/detailed_email_extension.dart +++ b/lib/features/email/domain/extensions/detailed_email_extension.dart @@ -1,5 +1,9 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/extensions/account_id_extensions.dart'; import 'package:model/extensions/email_id_extensions.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart'; import 'package:tmail_ui_user/features/email/domain/extensions/list_email_header_extension.dart'; @@ -21,9 +25,15 @@ extension DetailedEmailExtension on DetailedEmail { ); } - String get newEmailFolderPath => CachingConstants.newEmailsContentFolderName; + String getNewEmailFolderPath(AccountId accountId, UserName userName) { + final folderKey = TupleKey(accountId.asString, userName.value).encodeKey; + return '${CachingConstants.newEmailsContentFolderName}/$folderKey'; + } - String get openedEmailFolderPath => CachingConstants.openedEmailContentFolderName; + String getOpenedEmailFolderPath(AccountId accountId, UserName userName) { + final folderKey = TupleKey(accountId.asString, userName.value).encodeKey; + return '${CachingConstants.openedEmailContentFolderName}/$folderKey'; + } DetailedEmail fromEmailContentPath(String path) { return DetailedEmail( diff --git a/lib/features/login/presentation/model/login_navigate_type.dart b/lib/features/login/presentation/model/login_navigate_type.dart index e110545742..ee49799693 100644 --- a/lib/features/login/presentation/model/login_navigate_type.dart +++ b/lib/features/login/presentation/model/login_navigate_type.dart @@ -2,5 +2,6 @@ enum LoginNavigateType { signIn, addAnotherAccount, - switchActiveAccount; + switchActiveAccount, + selectActiveAccount; } \ No newline at end of file diff --git a/lib/features/mailbox/data/local/mailbox_cache_manager.dart b/lib/features/mailbox/data/local/mailbox_cache_manager.dart index f41c87e5a2..4af5270903 100644 --- a/lib/features/mailbox/data/local/mailbox_cache_manager.dart +++ b/lib/features/mailbox/data/local/mailbox_cache_manager.dart @@ -1,10 +1,12 @@ +import 'package:collection/collection.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/extensions/account_id_extensions.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/caching/clients/mailbox_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/list_mailbox_cache_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/list_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/list_mailbox_id_extension.dart'; @@ -17,7 +19,8 @@ class MailboxCacheManager { MailboxCacheManager(this._mailboxCacheClient); Future> getAllMailbox(AccountId accountId, UserName userName) async { - final mailboxCacheList = await _mailboxCacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final mailboxCacheList = await _mailboxCacheClient.getListByNestedKey(nestedKey); return mailboxCacheList.toMailboxList(); } @@ -47,14 +50,11 @@ class MailboxCacheManager { } Future getSpamMailbox(AccountId accountId, UserName userName) async { - final mailboxCachedList = await _mailboxCacheClient.getListByTupleKey(accountId.asString, userName.value); - final listSpamMailboxCached = mailboxCachedList - .toMailboxList() - .where((mailbox) => mailbox.role == PresentationMailbox.roleSpam) - .toList(); - - if (listSpamMailboxCached.isNotEmpty) { - return listSpamMailboxCached.first; + final mailboxList = await getAllMailbox(accountId, userName); + final spamMailbox = mailboxList.firstWhereOrNull((mailbox) => mailbox.role == PresentationMailbox.roleSpam); + + if (spamMailbox != null) { + return spamMailbox; } else { throw NotFoundSpamMailboxCachedException(); } diff --git a/lib/features/offline_mode/manager/new_email_cache_manager.dart b/lib/features/offline_mode/manager/new_email_cache_manager.dart index 14cf96b0dc..d169d8bbe4 100644 --- a/lib/features/offline_mode/manager/new_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/new_email_cache_manager.dart @@ -60,7 +60,8 @@ class NewEmailCacheManager { } Future> getAllDetailedEmails(AccountId accountId, UserName userName) async { - final detailedEmailCacheList = await _cacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final detailedEmailCacheList = await _cacheClient.getListByNestedKey(nestedKey); detailedEmailCacheList.sortByLatestTime(); return detailedEmailCacheList; } diff --git a/lib/features/offline_mode/manager/opened_email_cache_manager.dart b/lib/features/offline_mode/manager/opened_email_cache_manager.dart index da50a1f69a..439b8fadbd 100644 --- a/lib/features/offline_mode/manager/opened_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/opened_email_cache_manager.dart @@ -40,7 +40,8 @@ class OpenedEmailCacheManager { } Future> getAllDetailedEmails(AccountId accountId, UserName userName) async { - final detailedEmailCacheList = await _cacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final detailedEmailCacheList = await _cacheClient.getListByNestedKey(nestedKey); detailedEmailCacheList.sortByLatestTime(); log('OpenedEmailCacheManager::getAllDetailedEmails():SIZE: ${detailedEmailCacheList.length}'); return detailedEmailCacheList; diff --git a/lib/features/offline_mode/manager/sending_email_cache_manager.dart b/lib/features/offline_mode/manager/sending_email_cache_manager.dart index 314bb06d04..5bee6c6cc6 100644 --- a/lib/features/offline_mode/manager/sending_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/sending_email_cache_manager.dart @@ -1,6 +1,7 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/account/personal_account.dart'; import 'package:model/extensions/account_id_extensions.dart'; import 'package:tmail_ui_user/features/caching/clients/sending_email_hive_cache_client.dart'; import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; @@ -29,8 +30,9 @@ class SendingEmailCacheManager { } } - Future> getAllSendingEmailsByTupleKey(AccountId accountId, UserName userName) async { - final sendingEmailsCache = await _hiveCacheClient.getListByTupleKey(accountId.asString, userName.value); + Future> getAllSendingEmailsByAccount(AccountId accountId, UserName userName) async { + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final sendingEmailsCache = await _hiveCacheClient.getListByNestedKey(nestedKey); sendingEmailsCache.sortByLatestTime(); return sendingEmailsCache; } @@ -52,6 +54,15 @@ class SendingEmailCacheManager { Future clearAllSendingEmails() => _hiveCacheClient.clearAllData(); + Future clearAllSendingEmailsByAccount(PersonalAccount currentAccount) async { + final nestedKey = TupleKey( + currentAccount.accountId!.asString, + currentAccount.userName!.value + ).encodeKey; + + await _hiveCacheClient.clearAllDataContainKey(nestedKey); + } + Future updateSendingEmail( AccountId accountId, UserName userName, diff --git a/lib/features/push_notification/domain/state/destroy_firebase_registration_state.dart b/lib/features/push_notification/domain/state/destroy_firebase_registration_state.dart index d661ea4c80..c9ea64407d 100644 --- a/lib/features/push_notification/domain/state/destroy_firebase_registration_state.dart +++ b/lib/features/push_notification/domain/state/destroy_firebase_registration_state.dart @@ -1,11 +1,24 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:model/account/personal_account.dart'; class DestroyFirebaseRegistrationLoading extends LoadingState {} -class DestroyFirebaseRegistrationSuccess extends UIState {} +class DestroyFirebaseRegistrationSuccess extends UIState { + + final PersonalAccount currentAccount; + + DestroyFirebaseRegistrationSuccess(this.currentAccount); + + @override + List get props => [currentAccount]; +} class DestroyFirebaseRegistrationFailure extends FeatureFailure { + final PersonalAccount currentAccount; + + DestroyFirebaseRegistrationFailure({dynamic exception, required this.currentAccount}) : super(exception: exception); - DestroyFirebaseRegistrationFailure(dynamic exception) : super(exception: exception); + @override + List get props => [...super.props, currentAccount]; } \ No newline at end of file diff --git a/lib/features/push_notification/domain/usecases/remove_firebase_registration_interactor.dart b/lib/features/push_notification/domain/usecases/remove_firebase_registration_interactor.dart index 817ca1c43e..1c02f38cf5 100644 --- a/lib/features/push_notification/domain/usecases/remove_firebase_registration_interactor.dart +++ b/lib/features/push_notification/domain/usecases/remove_firebase_registration_interactor.dart @@ -1,8 +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/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/account/personal_account.dart'; import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_repository.dart'; import 'package:tmail_ui_user/features/push_notification/domain/state/destroy_firebase_registration_state.dart'; @@ -12,17 +11,25 @@ class RemoveFirebaseRegistrationInteractor { RemoveFirebaseRegistrationInteractor(this._fcmRepository); - Stream> execute(AccountId accountId, UserName userName) async* { + Stream> execute(PersonalAccount currentAccount) async* { try { yield Right(DestroyFirebaseRegistrationLoading()); - final registration = await _fcmRepository.getStoredFirebaseRegistration(accountId, userName); + final registration = await _fcmRepository.getStoredFirebaseRegistration( + currentAccount.accountId!, + currentAccount.userName!); + await Future.wait([ _fcmRepository.destroyFirebaseRegistration(registration.id!), - _fcmRepository.deleteFirebaseRegistrationCache(accountId, userName), + _fcmRepository.deleteFirebaseRegistrationCache( + currentAccount.accountId!, + currentAccount.userName!), ], eagerError: true); - yield Right(DestroyFirebaseRegistrationSuccess()); + + yield Right(DestroyFirebaseRegistrationSuccess(currentAccount)); } catch (e) { - yield Left(DestroyFirebaseRegistrationFailure(e)); + yield Left(DestroyFirebaseRegistrationFailure( + exception: e, + currentAccount: currentAccount)); } } } \ No newline at end of file diff --git a/lib/features/thread/data/local/email_cache_manager.dart b/lib/features/thread/data/local/email_cache_manager.dart index cad8cc1372..b4fce7c020 100644 --- a/lib/features/thread/data/local/email_cache_manager.dart +++ b/lib/features/thread/data/local/email_cache_manager.dart @@ -31,7 +31,8 @@ class EmailCacheManager { UnsignedInt? limit, FilterMessageOption filterOption = FilterMessageOption.all }) async { - final emailCacheList = await _emailCacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final emailCacheList = await _emailCacheClient.getListByNestedKey(nestedKey); final emailList = emailCacheList .toEmailList() .where((email) => _filterEmailByMailbox(email, filterOption, inMailboxId)) diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 7522d9ccdf..d281b3095c 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -79,7 +79,6 @@ import 'package:tmail_ui_user/main/routes/dialog_router.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; -import 'package:tmail_ui_user/main/utils/authenticated_account_manager.dart'; typedef StartRangeSelection = int; typedef EndRangeSelection = int; @@ -87,7 +86,6 @@ typedef EndRangeSelection = int; class ThreadController extends BaseController with EmailActionController { final NetworkConnectionController networkConnectionController = Get.find(); - final AuthenticatedAccountManager authenticatedAccountManager = Get.find(); final GetEmailsInMailboxInteractor _getEmailsInMailboxInteractor; final RefreshChangesEmailsInMailboxInteractor _refreshChangesEmailsInMailboxInteractor; diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 5cedfec9bf..b77b039ab7 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-01-08T14:23:35.609263", + "@@last_modified": "2024-01-09T19:07:13.451605", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3677,5 +3677,15 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "unableToLogInToAccount": "Unable to log in to account {userName}", + "@unableToLogInToAccount": { + "type": "text", + "placeholders_order": [ + "userName" + ], + "placeholders": { + "userName": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 643fd2d9ed..44ec6d60bb 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3835,4 +3835,12 @@ class AppLocalizations { name: 'toastMessageFailureWhenSwitchActiveAccount', ); } + + String unableToLogInToAccount(String userName) { + return Intl.message( + 'Unable to log in to account $userName', + name: 'unableToLogInToAccount', + args: [userName] + ); + } } \ No newline at end of file diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart index bd5d32af2a..d435691071 100644 --- a/lib/main/utils/authenticated_account_manager.dart +++ b/lib/main/utils/authenticated_account_manager.dart @@ -21,6 +21,7 @@ typedef OnAddAnotherAccountAction = Function(PersonalAccount? currentAccount); typedef OnSwitchActiveAccountAction = Function( PersonalAccount currentActiveAccount, PersonalAccount nextActiveAccount); +typedef OnSelectActiveAccountAction = Function(PersonalAccount activeAccount); class AuthenticatedAccountManager { @@ -32,6 +33,18 @@ class AuthenticatedAccountManager { this._imagePaths, ); + Future> getAllPersonalAccount() { + return _getAllAuthenticatedAccountInteractor + .execute() + .then((result) => result.fold( + (failure) => [], + (success) => success is GetAllAuthenticatedAccountSuccess + ? success.listAccount + : [] + ) + ); + } + Future> _getAllTwakeMailPresentationAccount() { return _getAllAuthenticatedAccountInteractor .execute() @@ -82,6 +95,7 @@ class AuthenticatedAccountManager { VoidCallback? onGoToManageAccount, OnAddAnotherAccountAction? onAddAnotherAccountAction, OnSwitchActiveAccountAction? onSwitchActiveAccountAction, + OnSelectActiveAccountAction? onSelectActiveAccountAction, }) async { final listPresentationAccount = await _getAllTwakeMailPresentationAccount(); final currentActiveAccount = listPresentationAccount @@ -127,9 +141,13 @@ class AuthenticatedAccountManager { onGoToAccountSettings: () => onGoToManageAccount?.call(), onSetAccountAsActive: (presentationAccount) { if (presentationAccount is TwakeMailPresentationAccount) { - onSwitchActiveAccountAction?.call( - currentActiveAccount!.personalAccount, - presentationAccount.personalAccount); + if (currentActiveAccount != null) { + onSwitchActiveAccountAction?.call( + currentActiveAccount.personalAccount, + presentationAccount.personalAccount); + } else { + onSelectActiveAccountAction?.call(presentationAccount.personalAccount); + } } }, ); From bea937c9fa3926050a98227acb60551c62e218d2 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 9 Jan 2024 19:10:15 +0700 Subject: [PATCH 09/17] TF-2431 Select new active account in account picker after logout success Signed-off-by: dab246 --- .../home/presentation/home_controller.dart | 86 +++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart index 9ca03b014c..e594bdf7b9 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -32,6 +32,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/prev import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_receiver.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.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'; @@ -173,13 +174,19 @@ class HomeController extends ReloadableController { } void _handleLoginNavigateArguments(LoginNavigateArguments navigateArguments) async { - if (navigateArguments.navigateType == LoginNavigateType.switchActiveAccount) { - _switchActiveAccount( - navigateArguments.currentAccount!, - navigateArguments.sessionCurrentAccount!, - navigateArguments.nextActiveAccount!); - } else { - await _cleanupCache(); + switch (navigateArguments.navigateType) { + case LoginNavigateType.switchActiveAccount: + _switchActiveAccount( + navigateArguments.currentAccount!, + navigateArguments.sessionCurrentAccount!, + navigateArguments.nextActiveAccount!); + break; + case LoginNavigateType.selectActiveAccount: + _showListAccountPicker(); + break; + default: + await _cleanupCache(); + break; } } @@ -243,4 +250,69 @@ class HomeController extends ReloadableController { ) ); } + + void _showListAccountPicker() { + if (currentContext == null) { + logError('HomeController::_showListAccountPicker: context is null'); + return; + } + + authenticatedAccountManager.showAccountsBottomSheetModal( + context: currentContext!, + onSelectActiveAccountAction: _handleSelectActiveAccount + ); + } + + void _handleSelectActiveAccount(PersonalAccount activeAccount) { + setUpInterceptors(activeAccount); + + _sessionStreamSubscription = getSessionInteractor.execute( + accountId: activeAccount.accountId, + userName: activeAccount.userName + ).listen( + (viewState) { + viewState.fold( + (failure) => _handleGetSessionFailureWhenSelectActiveAccount( + activeAccount: activeAccount, + exception: failure), + (success) => success is GetSessionSuccess + ? _handleGetSessionSuccessWhenSelectActiveAccount(activeAccount, success.session) + : null, + ); + }, + onError: (error, stack) { + _handleGetSessionFailureWhenSelectActiveAccount( + activeAccount: activeAccount, + exception: error); + } + ); + } + + void _handleGetSessionSuccessWhenSelectActiveAccount( + PersonalAccount activeAccount, + Session sessionActiveAccount + ) async { + log('HomeController::_handleGetSessionSuccessWhenSelectActiveAccount:sessionActiveAccount: $sessionActiveAccount'); + await popAndPush( + RouteUtils.generateNavigationRoute(AppRoutes.dashboard), + arguments: SwitchActiveAccountArguments( + session: sessionActiveAccount, + nextActiveAccount: activeAccount, + ) + ); + } + + void _handleGetSessionFailureWhenSelectActiveAccount({ + required PersonalAccount activeAccount, + dynamic exception + }) async { + logError('HomeController::_handleGetSessionFailureWhenSelectActiveAccount:exception: $exception'); + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastErrorMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).unableToLogInToAccount(activeAccount.userName?.value ?? '')); + } + + _showListAccountPicker(); + } } \ No newline at end of file From 7e1500cf1599617f98009e39dea645c7b46938cf Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 9 Jan 2024 19:39:06 +0700 Subject: [PATCH 10/17] TF-2431 Always return network Mailbox/Emails data when cache error Signed-off-by: dab246 --- .../repository/mailbox_repository_impl.dart | 28 +++++++++++-------- .../domain/model/mailbox_response.dart | 6 +--- .../repository/thread_repository_impl.dart | 18 ++++++------ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart index 24148e2949..48f50d8d46 100644 --- a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart +++ b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart @@ -52,7 +52,7 @@ class MailboxRepositoryImpl extends MailboxRepository { if (localMailboxResponse.hasData()) { bool hasMoreChanges = true; - State? sinceState = localMailboxResponse.state!; + State? sinceState = localMailboxResponse.state; while(hasMoreChanges && sinceState != null) { final changesResponse = await mapDataSource[DataSourceType.network]!.getChanges(session, accountId, sinceState, properties: properties); @@ -76,6 +76,15 @@ class MailboxRepositoryImpl extends MailboxRepository { stateDataSource.saveState(accountId, session.username, changesResponse.newStateMailbox!.toStateCache(StateType.mailbox)), ]); } + + final newMailboxResponse = await Future.wait([ + mapDataSource[DataSourceType.local]!.getAllMailboxCache(accountId, session.username), + stateDataSource.getState(accountId, session.username, StateType.mailbox) + ]).then((List response) { + return MailboxResponse(mailboxes: response.first, state: response.last); + }); + + yield newMailboxResponse; } else { final mailboxResponse = await mapDataSource[DataSourceType.network]!.getAllMailbox(session, accountId); @@ -84,16 +93,9 @@ class MailboxRepositoryImpl extends MailboxRepository { if (mailboxResponse.state != null) stateDataSource.saveState(accountId, session.username, mailboxResponse.state!.toStateCache(StateType.mailbox)), ]); - } - final newMailboxResponse = await Future.wait([ - mapDataSource[DataSourceType.local]!.getAllMailboxCache(accountId, session.username), - stateDataSource.getState(accountId, session.username, StateType.mailbox) - ]).then((List response) { - return MailboxResponse(mailboxes: response.first, state: response.last); - }); - - yield newMailboxResponse; + yield mailboxResponse; + } } Future?> _combineMailboxCache({ @@ -159,7 +161,11 @@ class MailboxRepositoryImpl extends MailboxRepository { return MailboxResponse(mailboxes: response.first, state: response.last); }); - yield newMailboxResponse; + if (newMailboxResponse.hasData() == true) { + yield newMailboxResponse; + } else { + yield* getAllMailbox(session, accountId); + } } @override diff --git a/lib/features/mailbox/domain/model/mailbox_response.dart b/lib/features/mailbox/domain/model/mailbox_response.dart index 8b07a0e5e2..d113f28a13 100644 --- a/lib/features/mailbox/domain/model/mailbox_response.dart +++ b/lib/features/mailbox/domain/model/mailbox_response.dart @@ -12,11 +12,7 @@ class MailboxResponse with EquatableMixin { this.state }); - bool hasData() { - return mailboxes != null - && mailboxes!.isNotEmpty - && state != null; - } + bool hasData() => mailboxes?.isNotEmpty == true; @override List get props => [mailboxes, state]; diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index a3721f83e4..0b58b2ae73 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -87,8 +87,8 @@ class ThreadRepositoryImpl extends ThreadRepository { yield localEmailResponse; } - if (networkEmailResponse != null) { - await _updateEmailCache(accountId, session.username, newCreated: networkEmailResponse.emailList); + if (networkEmailResponse?.hasEmails() == true) { + await _updateEmailCache(accountId, session.username, newCreated: networkEmailResponse!.emailList); } if (localEmailResponse.hasState()) { @@ -101,11 +101,9 @@ class ThreadRepositoryImpl extends ThreadRepository { propertiesUpdated: propertiesUpdated ); } else { - if (networkEmailResponse != null) { - log('ThreadRepositoryImpl::getAllEmail(): filter = ${emailFilter?.mailboxId} no local state -> update from network: ${networkEmailResponse.state}'); - if (networkEmailResponse.state != null) { - await _updateState(accountId, session.username, networkEmailResponse.state!); - } + if (networkEmailResponse?.hasState() == true) { + log('ThreadRepositoryImpl::getAllEmail(): filter = ${emailFilter?.mailboxId} no local state -> update from network: ${networkEmailResponse?.state}'); + await _updateState(accountId, session.username, networkEmailResponse!.state!); } } @@ -122,7 +120,11 @@ class ThreadRepositoryImpl extends ThreadRepository { return EmailsResponse(emailList: response.first, state: response.last); }); - yield newEmailResponse; + if (newEmailResponse.hasEmails() == true) { + yield newEmailResponse; + } else { + yield networkEmailResponse ?? newEmailResponse; + } } bool _isApproveFilterOption(FilterMessageOption? filterOption, List? listEmailResponse) { From 404d910eae9ece5d21c867e354496c2cd4754764 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 9 Jan 2024 19:46:45 +0700 Subject: [PATCH 11/17] TF-2431 Handle add other account after logout current account success Signed-off-by: dab246 --- lib/features/home/presentation/home_controller.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart index e594bdf7b9..222783efc1 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -259,7 +259,8 @@ class HomeController extends ReloadableController { authenticatedAccountManager.showAccountsBottomSheetModal( context: currentContext!, - onSelectActiveAccountAction: _handleSelectActiveAccount + onSelectActiveAccountAction: _handleSelectActiveAccount, + onAddAnotherAccountAction: (_) => _handleAddOtherAccount() ); } @@ -315,4 +316,8 @@ class HomeController extends ReloadableController { _showListAccountPicker(); } + + void _handleAddOtherAccount() { + navigateToTwakeIdPage(); + } } \ No newline at end of file From 1929e1f67ec260a9aea06a365cc015b40f70cf08 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 15 Jan 2024 19:27:14 +0700 Subject: [PATCH 12/17] TF-2431 Show toast message when switch active account success Signed-off-by: dab246 --- .../home/presentation/home_controller.dart | 4 +++- .../mailbox_dashboard_controller.dart | 23 +++++++++++++++++++ lib/l10n/intl_messages.arb | 12 +++++++++- lib/main/localizations/app_localizations.dart | 8 +++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart index 222783efc1..4433666c84 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -208,7 +208,7 @@ class HomeController extends ReloadableController { session: sessionCurrentAccount, exception: failure), (success) => success is GetSessionSuccess - ? _handleGetSessionSuccessWhenSwitchActiveAccount(nextActiveAccount, success.session) + ? _handleGetSessionSuccessWhenSwitchActiveAccount(currentActiveAccount, nextActiveAccount, success.session) : null, ); }, @@ -223,6 +223,7 @@ class HomeController extends ReloadableController { } void _handleGetSessionSuccessWhenSwitchActiveAccount( + PersonalAccount currentActiveAccount, PersonalAccount nextActiveAccount, Session sessionActiveAccount ) async { @@ -231,6 +232,7 @@ class HomeController extends ReloadableController { RouteUtils.generateNavigationRoute(AppRoutes.dashboard), arguments: SwitchActiveAccountArguments( session: sessionActiveAccount, + currentAccount: currentActiveAccount, nextActiveAccount: nextActiveAccount, ) ); 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 cc83b49f28..0acfcf4c1f 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -2559,6 +2559,29 @@ class MailboxDashBoardController extends ReloadableController { _setUpComponentsFromSession(arguments.session); dispatchRoute(DashboardRoutes.thread); + + _showToastMessageSwitchActiveAccountSuccess( + previousActiveAccount: arguments.currentAccount!, + currentActiveAccount: arguments.nextActiveAccount!, + ); + } + + void _showToastMessageSwitchActiveAccountSuccess({ + required PersonalAccount previousActiveAccount, + required PersonalAccount currentActiveAccount + }) { + if (currentContext != null && currentOverlayContext != null) { + appToast.showToastMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).toastMessageSuccessWhenSwitchActiveAccount(currentActiveAccount.userName?.value ?? ''), + actionName: AppLocalizations.of(currentContext!).undo, + onActionClick: () {}, + leadingSVGIcon: imagePaths.icToastSuccessMessage, + leadingSVGIconColor: Colors.white, + backgroundColor: AppColor.toastSuccessBackgroundColor, + textColor: Colors.white, + actionIcon: SvgPicture.asset(imagePaths.icUndo)); + } } void _handleRestorePreviousActiveAccountAction(RestoreActiveAccountArguments arguments) { diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index b77b039ab7..7a0669e7ff 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-01-09T19:07:13.451605", + "@@last_modified": "2024-01-15T19:14:33.334184", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3687,5 +3687,15 @@ "placeholders": { "userName": {} } + }, + "toastMessageSuccessWhenSwitchActiveAccount": "You’ve been switched to your {userName} account.", + "@toastMessageSuccessWhenSwitchActiveAccount": { + "type": "text", + "placeholders_order": [ + "userName" + ], + "placeholders": { + "userName": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 44ec6d60bb..07f90936cd 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3843,4 +3843,12 @@ class AppLocalizations { args: [userName] ); } + + String toastMessageSuccessWhenSwitchActiveAccount(String userName) { + return Intl.message( + 'You’ve been switched to your $userName account.', + name: 'toastMessageSuccessWhenSwitchActiveAccount', + args: [userName] + ); + } } \ No newline at end of file From 6d1ccbf2292d8e0cc87fecf2a4d8fa3ff982960b Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jan 2024 10:31:05 +0700 Subject: [PATCH 13/17] TF-2431 Undo previous active account Signed-off-by: dab246 --- .../reloadable/reloadable_controller.dart | 19 +++++ .../home/presentation/home_controller.dart | 61 ++++++++++------ .../mailbox_dashboard_controller.dart | 71 ++++++++++++++----- .../restore_active_account_arguments.dart | 5 +- .../select_active_account_arguments.dart | 21 ++++++ .../switch_active_account_arguments.dart | 21 +++--- .../presentation/thread_controller.dart | 15 ---- .../thread/presentation/thread_view.dart | 8 ++- 8 files changed, 157 insertions(+), 64 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/model/select_active_account_arguments.dart diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index 8196c3fe87..9636e43e2f 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.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'; @@ -15,7 +16,10 @@ import 'package:tmail_ui_user/features/login/domain/usecases/add_account_id_to_a import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; +import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.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/utils/message_toast_utils.dart'; @@ -110,4 +114,19 @@ abstract class ReloadableController extends BaseController { userName )); } + + void switchActiveAccount({ + required PersonalAccount currentAccount, + required PersonalAccount nextAccount, + required Session sessionCurrentAccount, + }) async { + await popAndPush( + AppRoutes.home, + arguments: LoginNavigateArguments( + navigateType: LoginNavigateType.switchActiveAccount, + currentAccount: currentAccount, + sessionCurrentAccount: sessionCurrentAccount, + nextActiveAccount: nextAccount, + )); + } } \ 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 4433666c84..f093348600 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -30,6 +30,7 @@ import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_a import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.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/restore_active_account_arguments.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/select_active_account_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_receiver.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -191,31 +192,35 @@ class HomeController extends ReloadableController { } void _switchActiveAccount( - PersonalAccount currentActiveAccount, + PersonalAccount currentAccount, Session sessionCurrentAccount, - PersonalAccount nextActiveAccount + PersonalAccount nextAccount ) { - setUpInterceptors(nextActiveAccount); + setUpInterceptors(nextAccount); _sessionStreamSubscription = getSessionInteractor.execute( - accountId: nextActiveAccount.accountId, - userName: nextActiveAccount.userName + accountId: nextAccount.accountId, + userName: nextAccount.userName ).listen( (viewState) { viewState.fold( (failure) => _handleGetSessionFailureWhenSwitchActiveAccount( - currentActiveAccount: currentActiveAccount, + currentActiveAccount: currentAccount, session: sessionCurrentAccount, exception: failure), (success) => success is GetSessionSuccess - ? _handleGetSessionSuccessWhenSwitchActiveAccount(currentActiveAccount, nextActiveAccount, success.session) + ? _handleGetSessionSuccessWhenSwitchActiveAccount( + currentAccount, + nextAccount, + sessionCurrentAccount, + success.session) : null, ); }, onError: (error, stack) { logError('HomeController::_switchActiveAccount:Exception: $error | Stack: $stack'); _handleGetSessionFailureWhenSwitchActiveAccount( - currentActiveAccount: currentActiveAccount, + currentActiveAccount: currentAccount, session: sessionCurrentAccount, exception: error); } @@ -223,36 +228,52 @@ class HomeController extends ReloadableController { } void _handleGetSessionSuccessWhenSwitchActiveAccount( - PersonalAccount currentActiveAccount, - PersonalAccount nextActiveAccount, - Session sessionActiveAccount + PersonalAccount currentAccount, + PersonalAccount nextAccount, + Session sessionCurrentAccount, + Session sessionNextAccount, ) async { - log('HomeController::_handleGetSessionSuccessWhenSwitchActiveAccount:sessionActiveAccount: $sessionActiveAccount'); + log('HomeController::_handleGetSessionSuccessWhenSwitchActiveAccount:sessionNextAccount: $sessionNextAccount'); await popAndPush( RouteUtils.generateNavigationRoute(AppRoutes.dashboard), arguments: SwitchActiveAccountArguments( - session: sessionActiveAccount, - currentAccount: currentActiveAccount, - nextActiveAccount: nextActiveAccount, + sessionCurrentAccount: sessionCurrentAccount, + sessionNextAccount: sessionNextAccount, + currentAccount: currentAccount, + nextAccount: nextAccount, ) ); } - void _handleGetSessionFailureWhenSwitchActiveAccount({ + void _restoreActiveAccount({ required PersonalAccount currentActiveAccount, required Session session, dynamic exception }) async { - logError('HomeController::_handleGetSessionFailureWhenSwitchActiveAccount:exception: $exception'); + logError('HomeController::_restoreActiveAccount:currentActiveAccount: $currentActiveAccount'); await popAndPush( RouteUtils.generateNavigationRoute(AppRoutes.dashboard), arguments: RestoreActiveAccountArguments( currentAccount: currentActiveAccount, - session: session + session: session, + exception: exception ) ); } + void _handleGetSessionFailureWhenSwitchActiveAccount({ + required PersonalAccount currentActiveAccount, + required Session session, + dynamic exception + }) async { + logError('HomeController::_handleGetSessionFailureWhenSwitchActiveAccount:exception: $exception'); + _restoreActiveAccount( + currentActiveAccount: currentActiveAccount, + session: session, + exception: exception + ); + } + void _showListAccountPicker() { if (currentContext == null) { logError('HomeController::_showListAccountPicker: context is null'); @@ -298,9 +319,9 @@ class HomeController extends ReloadableController { log('HomeController::_handleGetSessionSuccessWhenSelectActiveAccount:sessionActiveAccount: $sessionActiveAccount'); await popAndPush( RouteUtils.generateNavigationRoute(AppRoutes.dashboard), - arguments: SwitchActiveAccountArguments( + arguments: SelectActiveAccountArguments( session: sessionActiveAccount, - nextActiveAccount: activeAccount, + activeAccount: activeAccount, ) ); } 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 0acfcf4c1f..c6ed6cc8cc 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -100,6 +100,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/rest 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'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/select_active_account_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart'; import 'package:tmail_ui_user/features/mailto/presentation/model/mailto_arguments.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_vacation_state.dart'; @@ -486,7 +487,7 @@ 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) { @@ -495,6 +496,8 @@ class MailboxDashBoardController extends ReloadableController { _handleOpenEmailAction(arguments); } else if (arguments is SwitchActiveAccountArguments) { _handleSwitchActiveAccountAction(arguments); + } else if (arguments is SelectActiveAccountArguments) { + _handleSelectActiveAccountAction(arguments); } else if (arguments is RestoreActiveAccountArguments) { _handleRestorePreviousActiveAccountAction(arguments); } else { @@ -2548,34 +2551,54 @@ class MailboxDashBoardController extends ReloadableController { isRecoveringDeletedMessage.value = true; } - void _handleSwitchActiveAccountAction(SwitchActiveAccountArguments arguments) { - log('MailboxDashBoardController::_handleSwitchActiveAccountAction:arguments: $arguments'); + void _loadActiveAccount({ + required PersonalAccount activeAccount, + required Session sessionActiveAccount + }) { dispatchRoute(DashboardRoutes.waiting); - setCurrentAccountActive(arguments.nextActiveAccount!); + setCurrentAccountActive(activeAccount); - dynamicUrlInterceptors.changeBaseUrl(arguments.nextActiveAccount!.apiUrl); + dynamicUrlInterceptors.changeBaseUrl(activeAccount.apiUrl); - _setUpComponentsFromSession(arguments.session); + _setUpComponentsFromSession(sessionActiveAccount); dispatchRoute(DashboardRoutes.thread); + } + + void _handleSwitchActiveAccountAction(SwitchActiveAccountArguments arguments) { + log('MailboxDashBoardController::_handleSwitchActiveAccountAction:arguments: $arguments'); + _loadActiveAccount( + activeAccount: arguments.nextAccount, + sessionActiveAccount: arguments.sessionNextAccount + ); _showToastMessageSwitchActiveAccountSuccess( - previousActiveAccount: arguments.currentAccount!, - currentActiveAccount: arguments.nextActiveAccount!, + previousAccount: arguments.currentAccount, + currentAccount: arguments.nextAccount, + sessionPreviousAccount: arguments.sessionCurrentAccount, + ); + } + + void _handleSelectActiveAccountAction(SelectActiveAccountArguments arguments) { + log('MailboxDashBoardController::_handleSelectActiveAccountAction:arguments: $arguments'); + _loadActiveAccount( + activeAccount: arguments.activeAccount, + sessionActiveAccount: arguments.session ); } void _showToastMessageSwitchActiveAccountSuccess({ - required PersonalAccount previousActiveAccount, - required PersonalAccount currentActiveAccount + required PersonalAccount previousAccount, + required PersonalAccount currentAccount, + required Session sessionPreviousAccount, }) { if (currentContext != null && currentOverlayContext != null) { appToast.showToastMessage( currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageSuccessWhenSwitchActiveAccount(currentActiveAccount.userName?.value ?? ''), + AppLocalizations.of(currentContext!).toastMessageSuccessWhenSwitchActiveAccount(currentAccount.userName?.value ?? ''), actionName: AppLocalizations.of(currentContext!).undo, - onActionClick: () {}, + onActionClick: () => _undoSwitchActiveAccountAction(previousAccount, sessionPreviousAccount), leadingSVGIcon: imagePaths.icToastSuccessMessage, leadingSVGIconColor: Colors.white, backgroundColor: AppColor.toastSuccessBackgroundColor, @@ -2586,23 +2609,35 @@ class MailboxDashBoardController extends ReloadableController { void _handleRestorePreviousActiveAccountAction(RestoreActiveAccountArguments arguments) { log('MailboxDashBoardController::_handleRestorePreviousActiveAccountAction:arguments: $arguments'); - - if (currentContext != null && currentOverlayContext != null) { + if (arguments.exception != null && + currentContext != null && + currentOverlayContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).toastMessageFailureWhenSwitchActiveAccount); } + _loadActiveAccount( + activeAccount: arguments.currentAccount, + sessionActiveAccount: arguments.session + ); + } + + void _undoSwitchActiveAccountAction( + PersonalAccount previousAccount, + Session sessionPreviousAccount + ) { + setUpInterceptors(previousAccount); + dispatchRoute(DashboardRoutes.waiting); - setCurrentAccountActive(arguments.currentAccount); + setCurrentAccountActive(previousAccount); - dynamicUrlInterceptors.changeBaseUrl(arguments.currentAccount.apiUrl); + dynamicUrlInterceptors.changeBaseUrl(previousAccount.apiUrl); - _setUpComponentsFromSession(arguments.session); + _setUpComponentsFromSession(sessionPreviousAccount); dispatchRoute(DashboardRoutes.thread); - } @override diff --git a/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart index 0ea75a2aa4..a92539cd1a 100644 --- a/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart +++ b/lib/features/mailbox_dashboard/presentation/model/restore_active_account_arguments.dart @@ -7,15 +7,18 @@ class RestoreActiveAccountArguments extends RouterArguments { final PersonalAccount currentAccount; final Session session; + final dynamic exception; RestoreActiveAccountArguments({ required this.currentAccount, required this.session, + this.exception, }); @override - List get props => [ + List get props => [ currentAccount, session, + exception, ]; } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/model/select_active_account_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/select_active_account_arguments.dart new file mode 100644 index 0000000000..70a02b587e --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/model/select_active_account_arguments.dart @@ -0,0 +1,21 @@ + +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 SelectActiveAccountArguments extends RouterArguments { + + final Session session; + final PersonalAccount activeAccount; + + SelectActiveAccountArguments({ + required this.session, + required this.activeAccount, + }); + + @override + List get props => [ + session, + activeAccount, + ]; +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart b/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart index 94e18673f5..df6452d2d1 100644 --- a/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart +++ b/lib/features/mailbox_dashboard/presentation/model/switch_active_account_arguments.dart @@ -5,20 +5,23 @@ import 'package:tmail_ui_user/main/routes/router_arguments.dart'; class SwitchActiveAccountArguments extends RouterArguments { - final Session session; - final PersonalAccount? currentAccount; - final PersonalAccount? nextActiveAccount; + final Session sessionCurrentAccount; + final Session sessionNextAccount; + final PersonalAccount currentAccount; + final PersonalAccount nextAccount; SwitchActiveAccountArguments({ - required this.session, - this.currentAccount, - this.nextActiveAccount, + required this.sessionCurrentAccount, + required this.sessionNextAccount, + required this.currentAccount, + required this.nextAccount, }); @override - List get props => [ - session, + List get props => [ + sessionCurrentAccount, + sessionNextAccount, currentAccount, - nextActiveAccount, + nextAccount, ]; } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index d281b3095c..9ff3a37118 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1254,19 +1254,4 @@ class ThreadController extends BaseController with EmailActionController { ); log('ThreadController::addAnotherAccount:result: $result'); } - - void switchActiveAccount( - PersonalAccount currentActiveAccount, - PersonalAccount nextActiveAccount - ) async { - await popAndPush( - AppRoutes.home, - arguments: LoginNavigateArguments( - navigateType: LoginNavigateType.switchActiveAccount, - currentAccount: currentActiveAccount, - sessionCurrentAccount: mailboxDashBoardController.sessionCurrent, - nextActiveAccount: nextActiveAccount, - ) - ); - } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 179eb3a57b..788d4d10f9 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -107,7 +107,13 @@ class ThreadView extends GetWidget context: context, onGoToManageAccount: controller.mailboxDashBoardController.goToSettings, onAddAnotherAccountAction: controller.addAnotherAccount, - onSwitchActiveAccountAction: controller.switchActiveAccount + onSwitchActiveAccountAction: (currentAccount, nextAccount) { + controller.mailboxDashBoardController.switchActiveAccount( + currentAccount: currentAccount, + nextAccount: nextAccount, + sessionCurrentAccount: controller.mailboxDashBoardController.sessionCurrent! + ); + } ); }, ); From 1e977373e167151af3ffa5b58da028c5988b0f61 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jan 2024 10:37:14 +0700 Subject: [PATCH 14/17] TF-2431 Rename `AccountActive` to `ActiveAccount` Signed-off-by: dab246 --- lib/features/base/base_controller.dart | 8 ++++---- .../login/data/datasource/account_datasource.dart | 2 +- .../hive_account_datasource_impl.dart | 4 ++-- .../login/data/local/account_cache_manager.dart | 6 +++--- .../data/repository/account_repository_impl.dart | 4 ++-- .../login/domain/repository/account_repository.dart | 2 +- .../state/set_current_account_active_state.dart | 8 ++++---- ...rt => set_current_active_account_interactor.dart} | 12 ++++++------ .../controller/mailbox_dashboard_controller.dart | 4 ++-- .../bindings/credential/credential_bindings.dart | 4 ++-- 10 files changed, 27 insertions(+), 27 deletions(-) rename lib/features/login/domain/usecases/{set_current_account_active_interactor.dart => set_current_active_account_interactor.dart} (63%) diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 2c71bad12b..1584db51c3 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -31,7 +31,7 @@ import 'package:tmail_ui_user/features/login/domain/state/logout_current_account import 'package:tmail_ui_user/features/login/domain/state/logout_current_account_oidc_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/logout_current_account_state.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/set_current_account_active_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/set_current_active_account_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; @@ -79,7 +79,7 @@ abstract class BaseController extends GetxController final ResponsiveUtils responsiveUtils = Get.find(); final Uuid uuid = Get.find(); final AppStore appStore = Get.find(); - final SetCurrentAccountActiveInteractor _setCurrentAccountActiveInteractor = Get.find(); + final SetCurrentActiveAccountInteractor _setCurrentActiveAccountInteractor = Get.find(); final AuthenticatedAccountManager authenticatedAccountManager = Get.find(); final _fcmReceiver = FcmReceiver.instance; @@ -451,8 +451,8 @@ abstract class BaseController extends GetxController } } - void setCurrentAccountActive(PersonalAccount activeAccount) { - consumeState(_setCurrentAccountActiveInteractor.execute(activeAccount)); + void setCurrentActiveAccount(PersonalAccount activeAccount) { + consumeState(_setCurrentActiveAccountInteractor.execute(activeAccount)); } void _handleBadCredentials() async { diff --git a/lib/features/login/data/datasource/account_datasource.dart b/lib/features/login/data/datasource/account_datasource.dart index 0d704835bd..a1b0f8beef 100644 --- a/lib/features/login/data/datasource/account_datasource.dart +++ b/lib/features/login/data/datasource/account_datasource.dart @@ -9,5 +9,5 @@ abstract class AccountDatasource { Future> getAllAccount(); - Future setCurrentAccountActive(PersonalAccount activeAccount); + Future setCurrentActiveAccount(PersonalAccount activeAccount); } \ No newline at end of file diff --git a/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart b/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart index 135df9527a..fc12216cf2 100644 --- a/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart +++ b/lib/features/login/data/datasource_impl/hive_account_datasource_impl.dart @@ -50,9 +50,9 @@ class HiveAccountDatasourceImpl extends AccountDatasource { } @override - Future setCurrentAccountActive(PersonalAccount activeAccount) { + Future setCurrentActiveAccount(PersonalAccount activeAccount) { return Future.sync(() async { - return await _accountCacheManager.setCurrentAccountActive(activeAccount); + return await _accountCacheManager.setCurrentActiveAccount(activeAccount); }).catchError(_exceptionThrower.throwException); } } \ No newline at end of file diff --git a/lib/features/login/data/local/account_cache_manager.dart b/lib/features/login/data/local/account_cache_manager.dart index 8eb31b7990..7135fa7cae 100644 --- a/lib/features/login/data/local/account_cache_manager.dart +++ b/lib/features/login/data/local/account_cache_manager.dart @@ -58,11 +58,11 @@ class AccountCacheManager { } } - Future setCurrentAccountActive(PersonalAccount activeAccount) async { - log('AccountCacheManager::setCurrentAccountActive(): $activeAccount'); + Future setCurrentActiveAccount(PersonalAccount activeAccount) async { + log('AccountCacheManager::setCurrentActiveAccount(): $activeAccount'); final newAccountCache = activeAccount.toCache(isSelected: true); final allAccounts = await _accountCacheClient.getAll(); - log('AccountCacheManager::setCurrentAccountActive::allAccounts(): $allAccounts'); + log('AccountCacheManager::setCurrentActiveAccount::allAccounts(): $allAccounts'); if (allAccounts.isNotEmpty) { final newAllAccounts = allAccounts.unselected().toList(); if (newAllAccounts.isNotEmpty) { diff --git a/lib/features/login/data/repository/account_repository_impl.dart b/lib/features/login/data/repository/account_repository_impl.dart index 6c496419ff..e9a35af0ad 100644 --- a/lib/features/login/data/repository/account_repository_impl.dart +++ b/lib/features/login/data/repository/account_repository_impl.dart @@ -29,7 +29,7 @@ class AccountRepositoryImpl extends AccountRepository { } @override - Future setCurrentAccountActive(PersonalAccount activeAccount) { - return _accountDatasource.setCurrentAccountActive(activeAccount); + Future setCurrentActiveAccount(PersonalAccount activeAccount) { + return _accountDatasource.setCurrentActiveAccount(activeAccount); } } \ No newline at end of file diff --git a/lib/features/login/domain/repository/account_repository.dart b/lib/features/login/domain/repository/account_repository.dart index 63bd3145d4..7c957a5cae 100644 --- a/lib/features/login/domain/repository/account_repository.dart +++ b/lib/features/login/domain/repository/account_repository.dart @@ -10,5 +10,5 @@ abstract class AccountRepository { Future> getAllAccount(); - Future setCurrentAccountActive(PersonalAccount activeAccount); + Future setCurrentActiveAccount(PersonalAccount activeAccount); } \ No newline at end of file diff --git a/lib/features/login/domain/state/set_current_account_active_state.dart b/lib/features/login/domain/state/set_current_account_active_state.dart index dbdc1d7aad..774ef99d31 100644 --- a/lib/features/login/domain/state/set_current_account_active_state.dart +++ b/lib/features/login/domain/state/set_current_account_active_state.dart @@ -1,11 +1,11 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; -class SetCurrentAccountActiveLoading extends LoadingState {} +class SetCurrentActiveAccountLoading extends LoadingState {} -class SetCurrentAccountActiveSuccess extends UIState {} +class SetCurrentActiveAccountSuccess extends UIState {} -class SetCurrentAccountActiveFailure extends FeatureFailure { +class SetCurrentActiveAccountFailure extends FeatureFailure { - SetCurrentAccountActiveFailure(dynamic exception) : super(exception: exception); + SetCurrentActiveAccountFailure(dynamic exception) : super(exception: exception); } \ No newline at end of file diff --git a/lib/features/login/domain/usecases/set_current_account_active_interactor.dart b/lib/features/login/domain/usecases/set_current_active_account_interactor.dart similarity index 63% rename from lib/features/login/domain/usecases/set_current_account_active_interactor.dart rename to lib/features/login/domain/usecases/set_current_active_account_interactor.dart index 3c64f0b39b..57075a469c 100644 --- a/lib/features/login/domain/usecases/set_current_account_active_interactor.dart +++ b/lib/features/login/domain/usecases/set_current_active_account_interactor.dart @@ -5,18 +5,18 @@ import 'package:model/account/personal_account.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/state/set_current_account_active_state.dart'; -class SetCurrentAccountActiveInteractor { +class SetCurrentActiveAccountInteractor { final AccountRepository _accountRepository; - SetCurrentAccountActiveInteractor(this._accountRepository); + SetCurrentActiveAccountInteractor(this._accountRepository); Stream> execute(PersonalAccount activeAccount) async* { try{ - yield Right(SetCurrentAccountActiveLoading()); - await _accountRepository.setCurrentAccountActive(activeAccount); - yield Right(SetCurrentAccountActiveSuccess()); + yield Right(SetCurrentActiveAccountLoading()); + await _accountRepository.setCurrentActiveAccount(activeAccount); + yield Right(SetCurrentActiveAccountSuccess()); } catch(e) { - yield Left(SetCurrentAccountActiveFailure(e)); + yield Left(SetCurrentActiveAccountFailure(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 c6ed6cc8cc..bea3a45bf1 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -2557,7 +2557,7 @@ class MailboxDashBoardController extends ReloadableController { }) { dispatchRoute(DashboardRoutes.waiting); - setCurrentAccountActive(activeAccount); + setCurrentActiveAccount(activeAccount); dynamicUrlInterceptors.changeBaseUrl(activeAccount.apiUrl); @@ -2631,7 +2631,7 @@ class MailboxDashBoardController extends ReloadableController { dispatchRoute(DashboardRoutes.waiting); - setCurrentAccountActive(previousAccount); + setCurrentActiveAccount(previousAccount); dynamicUrlInterceptors.changeBaseUrl(previousAccount.apiUrl); diff --git a/lib/main/bindings/credential/credential_bindings.dart b/lib/main/bindings/credential/credential_bindings.dart index 494a2ead48..3ca7dade5c 100644 --- a/lib/main/bindings/credential/credential_bindings.dart +++ b/lib/main/bindings/credential/credential_bindings.dart @@ -22,7 +22,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_a import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_basic_auth_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/logout_current_account_oidc_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/usecases/set_current_account_active_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/set_current_active_account_interactor.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; @@ -47,7 +47,7 @@ class CredentialBindings extends InteractorsBindings { Get.find() )); Get.put(AddAccountIdToActiveAccountInteractor(Get.find())); - Get.put(SetCurrentAccountActiveInteractor(Get.find())); + Get.put(SetCurrentActiveAccountInteractor(Get.find())); } @override From f48b330fc37598a288fac5dc72291c54e30c8345 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jan 2024 11:50:48 +0700 Subject: [PATCH 15/17] TF-2431 Add `SwitchAccounts` button in Setting Signed-off-by: dab246 --- assets/images/ic_switch_account.svg | 14 ++++ .../presentation/resources/image_paths.dart | 1 + core/lib/presentation/utils/theme_utils.dart | 12 ++-- .../settings/settings_first_level_view.dart | 33 +++------ .../menu/settings/settings_view.dart | 16 ++--- .../presentation/menu/settings_utils.dart | 12 +++- .../menu/widgets/account_profile_widget.dart | 72 +++++++++++++++++++ lib/l10n/intl_messages.arb | 8 ++- lib/main/localizations/app_localizations.dart | 7 ++ 9 files changed, 135 insertions(+), 40 deletions(-) create mode 100644 assets/images/ic_switch_account.svg create mode 100644 lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart diff --git a/assets/images/ic_switch_account.svg b/assets/images/ic_switch_account.svg new file mode 100644 index 0000000000..8d71fee1ae --- /dev/null +++ b/assets/images/ic_switch_account.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 95ab996b21..b697145b7f 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -212,6 +212,7 @@ class ImagePaths { String get icCheckboxOn => _getImagePath('ic_checkbox_on.svg'); String get icCheckboxOff => _getImagePath('ic_checkbox_off.svg'); String get icLogoTwakeHorizontal => _getImagePath('ic_logo_twake_horizontal.svg'); + String get icSwitchAccount => _getImagePath('ic_switch_account.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index bfa024504e..cd1972ba1a 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -22,22 +22,22 @@ class ThemeUtils { static TextTheme get _textTheme { return const TextTheme( - bodyLarge: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 17, - letterSpacing: -0.15, - ), titleLarge: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 28 ), + bodyLarge: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17 + ), bodyMedium: TextStyle( fontWeight: FontWeight.w500, fontSize: 15 ), bodySmall: TextStyle( - fontWeight: FontWeight.w500 + fontWeight: FontWeight.normal, + fontSize: 15 ), headlineLarge: TextStyle( fontWeight: FontWeight.w600, diff --git a/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart b/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart index c6e0e88c74..79b4b5be9d 100644 --- a/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart +++ b/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart @@ -3,9 +3,9 @@ import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart'; -import 'package:tmail_ui_user/features/mailbox/presentation/widgets/user_information_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings/settings_controller.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/menu/widgets/account_profile_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/widgets/setting_first_level_tile_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/account_menu_item.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -18,16 +18,11 @@ class SettingsFirstLevelView extends GetWidget { final child = SingleChildScrollView( controller: PlatformInfo.isMobile ? null : controller.settingScrollController, child: Column(children: [ - Obx(() => UserInformationWidget( + Obx(() => AccountProfileWidget( + imagePaths: controller.imagePaths, userProfile: controller.manageAccountDashboardController.userProfile.value, - padding: SettingsUtils.getPaddingInFirstLevel(context, controller.responsiveUtils), - titlePadding: const EdgeInsetsDirectional.only(start: 16))), - Divider( - color: AppColor.colorDividerHorizontal, - height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) - ), + padding: SettingsUtils.getPaddingInFirstLevel(context, controller.responsiveUtils))), + const Divider(), SettingFirstLevelTileBuilder( AppLocalizations.of(context).profiles, AccountMenuItem.profiles.getIcon(controller.imagePaths), @@ -37,8 +32,7 @@ class SettingsFirstLevelView extends GetWidget { Divider( color: AppColor.colorDividerHorizontal, height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) + indent: SettingsUtils.getDividerHorizontalPadding(context, controller.responsiveUtils) ), Obx(() { if (controller.manageAccountDashboardController.isRuleFilterCapabilitySupported) { @@ -52,8 +46,7 @@ class SettingsFirstLevelView extends GetWidget { Divider( color: AppColor.colorDividerHorizontal, height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) + indent: SettingsUtils.getDividerHorizontalPadding(context, controller.responsiveUtils) ), ]); } else { @@ -72,8 +65,7 @@ class SettingsFirstLevelView extends GetWidget { Divider( color: AppColor.colorDividerHorizontal, height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) + indent: SettingsUtils.getDividerHorizontalPadding(context, controller.responsiveUtils) ), ]); } else { @@ -92,8 +84,7 @@ class SettingsFirstLevelView extends GetWidget { Divider( color: AppColor.colorDividerHorizontal, height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) + indent: SettingsUtils.getDividerHorizontalPadding(context, controller.responsiveUtils) ), ]); } else { @@ -110,8 +101,7 @@ class SettingsFirstLevelView extends GetWidget { Divider( color: AppColor.colorDividerHorizontal, height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) + indent: SettingsUtils.getDividerHorizontalPadding(context, controller.responsiveUtils) ), ]), SettingFirstLevelTileBuilder( @@ -122,8 +112,7 @@ class SettingsFirstLevelView extends GetWidget { Divider( color: AppColor.colorDividerHorizontal, height: 1, - indent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils), - endIndent: SettingsUtils.getHorizontalPadding(context, controller.responsiveUtils) + indent: SettingsUtils.getDividerHorizontalPadding(context, controller.responsiveUtils) ), SettingFirstLevelTileBuilder( AppLocalizations.of(context).sign_out, diff --git a/lib/features/manage_account/presentation/menu/settings/settings_view.dart b/lib/features/manage_account/presentation/menu/settings/settings_view.dart index d6f83f8b1e..7144031044 100644 --- a/lib/features/manage_account/presentation/menu/settings/settings_view.dart +++ b/lib/features/manage_account/presentation/menu/settings/settings_view.dart @@ -1,8 +1,5 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/utils/style_utils.dart'; -import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/core.dart'; import 'package:core/utils/direction_utils.dart'; -import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -41,7 +38,7 @@ class SettingsView extends GetWidget { child: Padding( padding: SettingsUtils.getPaddingAppBar(context, controller.responsiveUtils), child: _buildAppbar(context))), - const Divider(color: AppColor.colorDividerComposer, height: 1), + const Divider(), Obx(() { if (controller.manageAccountDashboardController.vacationResponse.value?.vacationResponderIsValid == true) { return VacationNotificationMessageWidget( @@ -98,16 +95,17 @@ class SettingsView extends GetWidget { children: [ Positioned(left: 0,child: _buildCloseSettingButton(context)), Padding( - padding: const EdgeInsets.only(left: 50, right: 50), + padding: const EdgeInsets.symmetric(horizontal: 50), child: Text( AppLocalizations.of(context).settings, maxLines: 1, softWrap: CommonTextStyle.defaultSoftWrap, overflow: CommonTextStyle.defaultTextOverFlow, - style: const TextStyle( + style: Theme.of(context).textTheme.bodyLarge?.copyWith( fontSize: 20, - color: AppColor.colorNameEmail, - fontWeight: FontWeight.w700)) + color: Colors.black + ) + ) ) ] ); diff --git a/lib/features/manage_account/presentation/menu/settings_utils.dart b/lib/features/manage_account/presentation/menu/settings_utils.dart index 6e76620bcc..daca98adf1 100644 --- a/lib/features/manage_account/presentation/menu/settings_utils.dart +++ b/lib/features/manage_account/presentation/menu/settings_utils.dart @@ -13,11 +13,19 @@ class SettingsUtils { } } + static double getDividerHorizontalPadding(BuildContext context, ResponsiveUtils responsiveUtils) { + if (responsiveUtils.isMobile(context)) { + return 50; + } else { + return 66; + } + } + static EdgeInsets getPaddingInFirstLevel(BuildContext context, ResponsiveUtils responsiveUtils) { if (responsiveUtils.isMobile(context)) { - return const EdgeInsets.only(left: 16, top: 12, bottom: 12, right: 16); + return const EdgeInsets.only(left: 16, top: 16, bottom: 12, right: 16); } else { - return const EdgeInsets.only(left: 32, top: 12, bottom: 12, right: 32); + return const EdgeInsets.only(left: 32, top: 16, bottom: 12, right: 32); } } diff --git a/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart b/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart new file mode 100644 index 0000000000..512d447434 --- /dev/null +++ b/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart @@ -0,0 +1,72 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:core/presentation/views/image/avatar_builder.dart'; +import 'package:core/presentation/views/text/text_overflow_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:model/user/user_profile.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class AccountProfileWidget extends StatelessWidget { + final ImagePaths imagePaths; + final UserProfile? userProfile; + final VoidCallback? onOpenAccountPicker; + final EdgeInsetsGeometry? padding; + + const AccountProfileWidget({ + Key? key, + required this.imagePaths, + this.userProfile, + this.onOpenAccountPicker, + this.padding, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding ?? const EdgeInsetsDirectional.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AvatarBuilder( + text: userProfile?.getAvatarText() ?? '', + size: 56, + textColor: Colors.black, + bgColor: Colors.white, + boxShadows: const [ + BoxShadow( + color: AppColor.colorShadowBgContentEmail, + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 0.5) + ) + ] + ), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 16), + child: TextOverflowBuilder( + userProfile?.email ?? '', + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.black) + ), + ), + TMailButtonWidget( + text: AppLocalizations.of(context).switchAccounts, + icon: imagePaths.icSwitchAccount, + onTapActionCallback: onOpenAccountPicker, + mainAxisSize: MainAxisSize.min, + margin: const EdgeInsetsDirectional.only(start: 8), + backgroundColor: Colors.transparent, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: 16, + color: AppColor.primaryColor + )) + ])) + ] + ), + ); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 7a0669e7ff..a1feef9e9d 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-01-15T19:14:33.334184", + "@@last_modified": "2024-01-16T11:41:03.655960", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3697,5 +3697,11 @@ "placeholders": { "userName": {} } + }, + "switchAccounts": "Switch Accounts", + "@switchAccounts": { + "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 07f90936cd..c5237f30a3 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3851,4 +3851,11 @@ class AppLocalizations { args: [userName] ); } + + String get switchAccounts { + return Intl.message( + 'Switch Accounts', + name: 'switchAccounts', + ); + } } \ No newline at end of file From 176ba6d54276de88028b28f2d8d93e2490210131 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jan 2024 12:08:14 +0700 Subject: [PATCH 16/17] TF-2431 Switch active account from Setting View Signed-off-by: dab246 --- .../reloadable/reloadable_controller.dart | 26 ++++++++++++++++++- .../settings/settings_first_level_view.dart | 15 ++++++++++- .../presentation/thread_controller.dart | 13 ---------- .../thread/presentation/thread_view.dart | 5 ++-- lib/l10n/intl_messages.arb | 8 +++++- lib/main/localizations/app_localizations.dart | 6 +++++ .../utils/authenticated_account_manager.dart | 2 +- 7 files changed, 55 insertions(+), 20 deletions(-) diff --git a/lib/features/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart index 9636e43e2f..b9109e26a4 100644 --- a/lib/features/base/reloadable/reloadable_controller.dart +++ b/lib/features/base/reloadable/reloadable_controller.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; @@ -21,6 +22,7 @@ import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_t import 'package:tmail_ui_user/main/localizations/app_localizations.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/utils/authenticated_account_manager.dart'; import 'package:tmail_ui_user/main/utils/message_toast_utils.dart'; abstract class ReloadableController extends BaseController { @@ -120,7 +122,7 @@ abstract class ReloadableController extends BaseController { required PersonalAccount nextAccount, required Session sessionCurrentAccount, }) async { - await popAndPush( + await pushAndPopAll( AppRoutes.home, arguments: LoginNavigateArguments( navigateType: LoginNavigateType.switchActiveAccount, @@ -129,4 +131,26 @@ abstract class ReloadableController extends BaseController { nextActiveAccount: nextAccount, )); } + + void _addAnotherAccount(PersonalAccount? currentAccount) async { + await pushAndPopAll( + AppRoutes.twakeId, + arguments: LoginNavigateArguments( + navigateType: LoginNavigateType.addAnotherAccount, + currentAccount: currentAccount + )); + } + + Future showAccountPicker({ + required BuildContext context, + VoidCallback? goToSettingAction, + OnSwitchActiveAccountAction? onSwitchActiveAccountAction + }) async { + await authenticatedAccountManager.showAccountsBottomSheetModal( + context: context, + onGoToManageAccount: goToSettingAction, + onAddAnotherAccountAction: _addAnotherAccount, + onSwitchActiveAccountAction: onSwitchActiveAccountAction + ); + } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart b/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart index 79b4b5be9d..252f17ff47 100644 --- a/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart +++ b/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart @@ -21,7 +21,20 @@ class SettingsFirstLevelView extends GetWidget { Obx(() => AccountProfileWidget( imagePaths: controller.imagePaths, userProfile: controller.manageAccountDashboardController.userProfile.value, - padding: SettingsUtils.getPaddingInFirstLevel(context, controller.responsiveUtils))), + padding: SettingsUtils.getPaddingInFirstLevel(context, controller.responsiveUtils), + onOpenAccountPicker: () async { + await controller.manageAccountDashboardController.showAccountPicker( + context: context, + onSwitchActiveAccountAction: (currentAccount, nextAccount) { + controller.manageAccountDashboardController.switchActiveAccount( + currentAccount: currentAccount, + nextAccount: nextAccount, + sessionCurrentAccount: controller.manageAccountDashboardController.sessionCurrent! + ); + } + ); + }, + )), const Divider(), SettingFirstLevelTileBuilder( AppLocalizations.of(context).profiles, diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 9ff3a37118..634d88b214 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -29,8 +29,6 @@ import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state. import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; -import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_arguments.dart'; -import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; @@ -1243,15 +1241,4 @@ class ThreadController extends BaseController with EmailActionController { _searchEmail(); mailboxDashBoardController.clearDashBoardAction(); } - - void addAnotherAccount(PersonalAccount? currentAccount) async { - final result = await popAndPush( - AppRoutes.twakeId, - arguments: LoginNavigateArguments( - navigateType: LoginNavigateType.addAnotherAccount, - currentAccount: currentAccount - ) - ); - log('ThreadController::addAnotherAccount:result: $result'); - } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 788d4d10f9..19be0fcfbf 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -103,10 +103,9 @@ class ThreadView extends GetWidget ) : null, onOpenAccountPickerAction: () async { - await controller.authenticatedAccountManager.showAccountsBottomSheetModal( + await controller.mailboxDashBoardController.showAccountPicker( context: context, - onGoToManageAccount: controller.mailboxDashBoardController.goToSettings, - onAddAnotherAccountAction: controller.addAnotherAccount, + goToSettingAction: controller.mailboxDashBoardController.goToSettings, onSwitchActiveAccountAction: (currentAccount, nextAccount) { controller.mailboxDashBoardController.switchActiveAccount( currentAccount: currentAccount, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index a1feef9e9d..c69626d619 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-01-16T11:41:03.655960", + "@@last_modified": "2024-01-16T12:04:11.990592", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3703,5 +3703,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "accountSettings": "Account settings", + "@accountSettings": { + "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 c5237f30a3..9951e6a4b2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3858,4 +3858,10 @@ class AppLocalizations { name: 'switchAccounts', ); } + + String get accountSettings { + return Intl.message( + 'Account settings', + name: 'accountSettings'); + } } \ No newline at end of file diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart index d435691071..ab36ca5d03 100644 --- a/lib/main/utils/authenticated_account_manager.dart +++ b/lib/main/utils/authenticated_account_manager.dart @@ -106,7 +106,7 @@ class AuthenticatedAccountManager { accounts: listPresentationAccount, context: context, titleAddAnotherAccount: AppLocalizations.of(context).addAnotherAccount, - titleAccountSettings: AppLocalizations.of(context).manage_account, + titleAccountSettings: AppLocalizations.of(context).accountSettings, logoApp: Stack( children: [ Center(child: Padding( From a422ef429aed3ef65a42a00df25de976dc764559 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jan 2024 18:00:15 +0700 Subject: [PATCH 17/17] TF-2431 Show confirm dialog when logout Signed-off-by: dab246 --- core/lib/presentation/utils/theme_utils.dart | 1 - .../dialog/confirmation_dialog_builder.dart | 20 ++--- lib/features/base/base_controller.dart | 58 +++++++++++++- .../mixin/message_dialog_action_mixin.dart | 75 +++++++++---------- .../presentation/composer_controller.dart | 69 +++++++++-------- .../controller/single_email_controller.dart | 12 +-- .../mailbox_dashboard_view_web.dart | 1 + .../forward/forward_controller.dart | 16 ++-- .../manage_account_dashboard_view.dart | 1 + .../menu/manage_account_menu_view.dart | 1 + .../settings/settings_first_level_view.dart | 1 + .../menu/settings/settings_view.dart | 1 + .../menu/widgets/account_profile_widget.dart | 5 +- .../delete_identity_dialog_builder.dart | 2 +- .../sending_queue_controller.dart | 6 +- lib/l10n/intl_messages.arb | 20 ++++- lib/main/localizations/app_localizations.dart | 18 +++++ .../utils/authenticated_account_manager.dart | 6 +- 18 files changed, 209 insertions(+), 104 deletions(-) diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index cd1972ba1a..15c909bcf5 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -28,7 +28,6 @@ class ThemeUtils { fontSize: 28 ), bodyLarge: TextStyle( - fontWeight: FontWeight.bold, fontSize: 17 ), bodyMedium: TextStyle( diff --git a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart index 9f32053589..84e38e4c2c 100644 --- a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart @@ -23,12 +23,12 @@ class ConfirmDialogBuilder { TextStyle? _styleContent; double? _radiusButton; double? heightButton; - EdgeInsets? _paddingTitle; - EdgeInsets? _paddingContent; - EdgeInsets? _paddingButton; + EdgeInsetsGeometry? _paddingTitle; + EdgeInsetsGeometry? _paddingContent; + EdgeInsetsGeometry? _paddingButton; EdgeInsets? _outsideDialogPadding; - EdgeInsets? _marginIcon; - EdgeInsets? _margin; + EdgeInsetsGeometry? _marginIcon; + EdgeInsetsGeometry? _margin; double? _widthDialog; double? _heightDialog; double maxWith; @@ -95,23 +95,23 @@ class ConfirmDialogBuilder { _radiusButton = radius; } - void paddingTitle(EdgeInsets? value) { + void paddingTitle(EdgeInsetsGeometry? value) { _paddingTitle = value; } - void paddingContent(EdgeInsets? value) { + void paddingContent(EdgeInsetsGeometry? value) { _paddingContent = value; } - void paddingButton(EdgeInsets? value) { + void paddingButton(EdgeInsetsGeometry? value) { _paddingButton = value; } - void marginIcon(EdgeInsets? value) { + void marginIcon(EdgeInsetsGeometry? value) { _marginIcon = value; } - void margin(EdgeInsets? value) { + void margin(EdgeInsetsGeometry? value) { _margin = value; } diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 1584db51c3..b17a691a40 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -18,6 +18,7 @@ import 'package:get/get.dart'; 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/authentication_type.dart'; import 'package:model/account/personal_account.dart'; import 'package:rule_filter/rule_filter/capability_rule_filter.dart'; @@ -296,11 +297,66 @@ abstract class BaseController extends GetxController } void logout({ + required BuildContext context, required Session session, required AccountId accountId }) async { _isFcmEnabled = _isFcmActivated(session, accountId); - consumeState(_logoutCurrentAccountInteractor.execute()); + + if (PlatformInfo.isMobile) { + _showConfirmDialogLogout( + context: context, + userName: session.username + ); + } else { + consumeState(_logoutCurrentAccountInteractor.execute()); + } + } + + void _showConfirmDialogLogout({ + required BuildContext context, + required UserName userName + }) { + showConfirmDialogAction( + context: context, + actionName: AppLocalizations.of(currentContext!).yesLogout, + title: AppLocalizations.of(currentContext!).logoutConfirmation, + alignCenter: true, + titlePadding: const EdgeInsetsDirectional.only(top: 24), + messageStyle: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.colorTextBody, + fontSize: 15 + ), + titleStyle: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 20 + ), + actionButtonColor: AppColor.primaryColor, + actionStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontSize: 16 + ), + cancelStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppColor.primaryColor, + fontSize: 16 + ), + listTextSpan: [ + TextSpan(text: AppLocalizations.of(context).messageConfirmationLogout), + TextSpan( + text: ' ${userName.value}', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: AppColor.colorTextBody, + fontWeight: FontWeight.bold, + fontSize: 15 + ), + ), + const TextSpan(text: '?'), + ], + onConfirmAction: () { + consumeState(_logoutCurrentAccountInteractor.execute()); + }, + ); } void _removeFirebaseRegistration(PersonalAccount deletedAccount) async { diff --git a/lib/features/base/mixin/message_dialog_action_mixin.dart b/lib/features/base/mixin/message_dialog_action_mixin.dart index 1b93d13be9..e5d0230b82 100644 --- a/lib/features/base/mixin/message_dialog_action_mixin.dart +++ b/lib/features/base/mixin/message_dialog_action_mixin.dart @@ -8,28 +8,27 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; mixin MessageDialogActionMixin { - Future showConfirmDialogAction( - BuildContext context, - String message, - String actionName, - { - Function? onConfirmAction, - Function? onCancelAction, - String? title, - String? cancelTitle, - bool hasCancelButton = true, - bool showAsBottomSheet = false, - bool alignCenter = false, - List? listTextSpan, - Widget? icon, - TextStyle? titleStyle, - TextStyle? messageStyle, - TextStyle? actionStyle, - TextStyle? cancelStyle, - Color? actionButtonColor, - Color? cancelButtonColor, - } - ) async { + Future showConfirmDialogAction({ + required BuildContext context, + String? message, + String? actionName, + Function? onConfirmAction, + Function? onCancelAction, + String? title, + String? cancelTitle, + bool hasCancelButton = true, + bool showAsBottomSheet = false, + bool alignCenter = false, + List? listTextSpan, + Widget? icon, + TextStyle? titleStyle, + TextStyle? messageStyle, + TextStyle? actionStyle, + TextStyle? cancelStyle, + Color? actionButtonColor, + Color? cancelButtonColor, + EdgeInsetsGeometry? titlePadding, + }) async { final responsiveUtils = Get.find(); final imagePaths = Get.find(); @@ -39,20 +38,20 @@ mixin MessageDialogActionMixin { child: (ConfirmDialogBuilder(imagePaths, listTextSpan: listTextSpan, heightButton: 44) ..key(const Key('confirm_dialog_action')) ..title(title ?? '') - ..content(message) + ..content(message ?? '') ..addIcon(icon) ..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton) ..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton) ..marginIcon(icon != null ? const EdgeInsets.only(top: 24) : null) - ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : EdgeInsets.zero) + ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : titlePadding ?? EdgeInsets.zero) ..radiusButton(12) - ..paddingContent(const EdgeInsets.only(left: 24, right: 24, bottom: 24, top: 12)) - ..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 24, left: 24, right: 24)) + ..paddingContent(const EdgeInsetsDirectional.only(start: 24, end: 24, bottom: 24, top: 12)) + ..paddingButton(hasCancelButton ? null : const EdgeInsetsDirectional.only(bottom: 24, start: 24, end: 24)) ..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black)) ..styleContent(messageStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail)) ..styleTextCancelButton(cancelStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton)) ..styleTextConfirmButton(actionStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white)) - ..onConfirmButtonAction(actionName, () { + ..onConfirmButtonAction(actionName ?? AppLocalizations.of(context).yes, () { popBack(); onConfirmAction?.call(); }) @@ -80,21 +79,21 @@ mixin MessageDialogActionMixin { ) ..key(const Key('confirm_dialog_action')) ..title(title ?? '') - ..content(message) + ..content(message ?? '') ..addIcon(icon) ..margin(const EdgeInsets.only(bottom: 42)) ..widthDialog(responsiveUtils.getSizeScreenWidth(context)) ..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton) ..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton) - ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : EdgeInsets.zero) + ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : titlePadding ?? EdgeInsets.zero) ..marginIcon(EdgeInsets.zero) - ..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12)) - ..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44)) + ..paddingContent(const EdgeInsetsDirectional.only(start: 44, end: 44, bottom: 24, top: 12)) + ..paddingButton(hasCancelButton ? null : const EdgeInsetsDirectional.only(bottom: 16, start: 44, end: 44)) ..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black)) ..styleContent(messageStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail)) ..styleTextCancelButton(cancelStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton)) ..styleTextConfirmButton(actionStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white)) - ..onConfirmButtonAction(actionName, () { + ..onConfirmButtonAction(actionName ?? AppLocalizations.of(context).yes, () { popBack(); onConfirmAction?.call(); }) @@ -117,7 +116,7 @@ mixin MessageDialogActionMixin { ); } else { return (ConfirmationDialogActionSheetBuilder(context, listTextSpan: listTextSpan) - ..messageText(message) + ..messageText(message ?? '') ..styleConfirmButton(const TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: Colors.black)) ..styleMessage(messageStyle) ..styleCancelButton(cancelStyle) @@ -128,7 +127,7 @@ mixin MessageDialogActionMixin { onCancelAction?.call(); } ) - ..onConfirmAction(actionName, () { + ..onConfirmAction(actionName ?? AppLocalizations.of(context).yes, () { popBack(); onConfirmAction?.call(); })).show(); @@ -139,20 +138,20 @@ mixin MessageDialogActionMixin { child: (ConfirmDialogBuilder(imagePaths, listTextSpan: listTextSpan) ..key(const Key('confirm_dialog_action')) ..title(title ?? '') - ..content(message) + ..content(message ?? '') ..addIcon(icon) ..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton) ..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton) ..marginIcon(icon != null ? const EdgeInsets.only(top: 24) : null) - ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : EdgeInsets.zero) + ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : titlePadding ?? EdgeInsets.zero) ..marginIcon(EdgeInsets.zero) - ..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12)) + ..paddingContent(const EdgeInsetsDirectional.only(start: 44, end: 44, bottom: 24, top: 12)) ..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44)) ..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black)) ..styleContent(messageStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail)) ..styleTextCancelButton(cancelStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton)) ..styleTextConfirmButton(actionStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white)) - ..onConfirmButtonAction(actionName, () { + ..onConfirmButtonAction(actionName ?? AppLocalizations.of(context).yes, () { popBack(); onConfirmAction?.call(); }) diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 3037a84479..161a7b4266 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -832,9 +832,10 @@ class ComposerController extends BaseController { } if (!isEnableEmailSendButton.value) { - showConfirmDialogAction(context, - AppLocalizations.of(context).message_dialog_send_email_without_recipient, - AppLocalizations.of(context).add_recipients, + showConfirmDialogAction( + context: context, + message: AppLocalizations.of(context).message_dialog_send_email_without_recipient, + actionName: AppLocalizations.of(context).add_recipients, onConfirmAction: () => isSendEmailLoading.value = false, title: AppLocalizations.of(context).sending_failed, icon: SvgPicture.asset(imagePaths.icSendToastError, fit: BoxFit.fill), @@ -849,9 +850,10 @@ class ComposerController extends BaseController { .where((emailAddress) => !GetUtils.isEmail(emailAddress.emailAddress)) .toList(); if (listEmailAddressInvalid.isNotEmpty) { - showConfirmDialogAction(context, - AppLocalizations.of(context).message_dialog_send_email_with_email_address_invalid, - AppLocalizations.of(context).fix_email_addresses, + showConfirmDialogAction( + context: context, + message: AppLocalizations.of(context).message_dialog_send_email_with_email_address_invalid, + actionName: AppLocalizations.of(context).fix_email_addresses, onConfirmAction: () { toAddressExpandMode.value = ExpandMode.EXPAND; ccAddressExpandMode.value = ExpandMode.EXPAND; @@ -867,9 +869,10 @@ class ComposerController extends BaseController { } if (subjectEmail.value == null || subjectEmail.isEmpty == true) { - showConfirmDialogAction(context, - AppLocalizations.of(context).message_dialog_send_email_without_a_subject, - AppLocalizations.of(context).send_anyway, + showConfirmDialogAction( + context: context, + message: AppLocalizations.of(context).message_dialog_send_email_without_a_subject, + actionName: AppLocalizations.of(context).send_anyway, onConfirmAction: () => _handleSendMessages(context), onCancelAction: () => isSendEmailLoading.value = false, title: AppLocalizations.of(context).empty_subject, @@ -881,9 +884,9 @@ class ComposerController extends BaseController { if (!uploadController.allUploadAttachmentsCompleted) { showConfirmDialogAction( - context, - AppLocalizations.of(context).messageDialogSendEmailUploadingAttachment, - AppLocalizations.of(context).got_it, + context: context, + message: AppLocalizations.of(context).messageDialogSendEmailUploadingAttachment, + actionName: AppLocalizations.of(context).got_it, onConfirmAction: () => isSendEmailLoading.value = false, title: AppLocalizations.of(context).sending_failed, showAsBottomSheet: true, @@ -895,10 +898,10 @@ class ComposerController extends BaseController { if (!uploadController.hasEnoughMaxAttachmentSize()) { showConfirmDialogAction( - context, - AppLocalizations.of(context).message_dialog_send_email_exceeds_maximum_size( + context: context, + message: AppLocalizations.of(context).message_dialog_send_email_exceeds_maximum_size( filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), - AppLocalizations.of(context).got_it, + actionName: AppLocalizations.of(context).got_it, onConfirmAction: () => isSendEmailLoading.value = false, title: AppLocalizations.of(context).sending_failed, icon: SvgPicture.asset(imagePaths.icSendToastError, fit: BoxFit.fill), @@ -990,11 +993,11 @@ class ComposerController extends BaseController { void _showConfirmDialogStoreSendingEmail(BuildContext context) { showConfirmDialogAction( - context, - PlatformInfo.isIOS + context: context, + message: PlatformInfo.isIOS ? AppLocalizations.of(context).messageDialogOfflineModeOnIOS : '', - AppLocalizations.of(context).proceed, + actionName: AppLocalizations.of(context).proceed, onConfirmAction: () async { final sendingArgs = await _createSendingEmailArguments(context); _closeComposerAction( @@ -1155,10 +1158,10 @@ class ComposerController extends BaseController { } else { if (currentContext != null) { showConfirmDialogAction( - currentContext!, - AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( + context: currentContext!, + message: AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), - AppLocalizations.of(currentContext!).got_it, + actionName: AppLocalizations.of(currentContext!).got_it, onConfirmAction: () => {isSendEmailLoading.value = false}, title: AppLocalizations.of(currentContext!).maximum_files_size, hasCancelButton: false); @@ -1401,10 +1404,10 @@ class ComposerController extends BaseController { } else { if (currentContext != null) { showConfirmDialogAction( - currentContext!, - AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( + context: currentContext!, + message: AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), - AppLocalizations.of(currentContext!).got_it, + actionName: AppLocalizations.of(currentContext!).got_it, title: AppLocalizations.of(currentContext!).maximum_files_size, hasCancelButton: false, ); @@ -1888,10 +1891,10 @@ class ComposerController extends BaseController { } else { if (currentContext != null) { showConfirmDialogAction( - currentContext!, - AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( + context: currentContext!, + message: AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), - AppLocalizations.of(currentContext!).got_it, + actionName: AppLocalizations.of(currentContext!).got_it, onConfirmAction: () => {isSendEmailLoading.value = false}, title: AppLocalizations.of(currentContext!).maximum_files_size, hasCancelButton: false); @@ -2080,10 +2083,10 @@ class ComposerController extends BaseController { } else { if (currentContext != null) { showConfirmDialogAction( - currentContext!, - AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( + context: currentContext!, + message: AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), - AppLocalizations.of(currentContext!).got_it, + actionName: AppLocalizations.of(currentContext!).got_it, title: AppLocalizations.of(currentContext!).maximum_files_size, hasCancelButton: false, ); @@ -2189,10 +2192,10 @@ class ComposerController extends BaseController { } else { if (currentContext != null) { showConfirmDialogAction( - currentContext!, - AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( + context: currentContext!, + message: AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), - AppLocalizations.of(currentContext!).got_it, + actionName: AppLocalizations.of(currentContext!).got_it, title: AppLocalizations.of(currentContext!).maximum_files_size, hasCancelButton: false, ); diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 85570a7831..96e71a470e 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -531,9 +531,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void _handleReadReceipt() { if (currentContext != null) { - showConfirmDialogAction(currentContext!, - AppLocalizations.of(currentContext!).subTitleReadReceiptRequestNotificationMessage, - AppLocalizations.of(currentContext!).yes, + showConfirmDialogAction( + context: currentContext!, + message: AppLocalizations.of(currentContext!).subTitleReadReceiptRequestNotificationMessage, + actionName: AppLocalizations.of(currentContext!).yes, onConfirmAction: () => _handleSendReceiptToSenderAction(currentContext!), showAsBottomSheet: true, title: AppLocalizations.of(currentContext!).titleReadReceiptRequestNotificationMessage, @@ -1416,9 +1417,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) { showConfirmDialogAction( - context, - '', - AppLocalizations.of(context).unsubscribe, + context: context, + actionName: AppLocalizations.of(context).unsubscribe, onConfirmAction: () { if (emailUnsubscribe.value?.httpLinks.isNotEmpty == true) { _handleUnsubscribeMailByHttpsLink( diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 0040177e6d..9f1af184a2 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -365,6 +365,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { if (controller.sessionCurrent != null && controller.accountId.value != null) { controller.logout( + context: context, session: controller.sessionCurrent!, accountId: controller.accountId.value! ); diff --git a/lib/features/manage_account/presentation/forward/forward_controller.dart b/lib/features/manage_account/presentation/forward/forward_controller.dart index c24b5d9f29..c52d1e0377 100644 --- a/lib/features/manage_account/presentation/forward/forward_controller.dart +++ b/lib/features/manage_account/presentation/forward/forward_controller.dart @@ -8,6 +8,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:forward/forward/tmail_forward.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; @@ -16,7 +17,6 @@ import 'package:tmail_ui_user/features/manage_account/domain/model/delete_recipi import 'package:tmail_ui_user/features/manage_account/domain/model/edit_local_copy_in_forwarding_request.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/add_recipient_in_forwarding_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/delete_recipient_in_forwarding_state.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/edit_local_copy_in_forwarding_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/get_forward_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/add_recipients_in_forwarding_interactor.dart'; @@ -110,10 +110,11 @@ class ForwardController extends BaseController { } void deleteRecipients(BuildContext context, String emailAddress) { - showConfirmDialogAction(context, + showConfirmDialogAction( + context: context, title: AppLocalizations.of(context).deleteRecipient, - AppLocalizations.of(context).messageConfirmationDialogDeleteRecipientForward(emailAddress), - AppLocalizations.of(context).remove, + message: AppLocalizations.of(context).messageConfirmationDialogDeleteRecipientForward(emailAddress), + actionName: AppLocalizations.of(context).remove, onConfirmAction: () => _handleDeleteRecipientAction({emailAddress}), showAsBottomSheet: true, icon: SvgPicture.asset(imagePaths.icDeleteDialogRecipients, fit: BoxFit.fill), @@ -195,10 +196,11 @@ class ForwardController extends BaseController { } void deleteMultipleRecipients(BuildContext context, Set listEmailAddress) { - showConfirmDialogAction(currentContext!, + showConfirmDialogAction( + context: currentContext!, title: AppLocalizations.of(context).deleteRecipient, - AppLocalizations.of(context).messageConfirmationDialogDeleteAllRecipientForward, - AppLocalizations.of(currentContext!).remove, + message: AppLocalizations.of(context).messageConfirmationDialogDeleteAllRecipientForward, + actionName: AppLocalizations.of(currentContext!).remove, onConfirmAction: () => _handleDeleteRecipientAction(listEmailAddress), showAsBottomSheet: true, icon: SvgPicture.asset(imagePaths.icDeleteDialogRecipients, fit: BoxFit.fill), diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart index 97460b0bb2..d6ffb8f235 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart @@ -154,6 +154,7 @@ class ManageAccountDashBoardView extends GetWidget { if (controller.dashBoardController.sessionCurrent != null && controller.dashBoardController.accountId.value != null) { controller.dashBoardController.logout( + context: context, session: controller.dashBoardController.sessionCurrent!, accountId: controller.dashBoardController.accountId.value! ); diff --git a/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart b/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart index 252f17ff47..f5cc03d35e 100644 --- a/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart +++ b/lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart @@ -134,6 +134,7 @@ class SettingsFirstLevelView extends GetWidget { if (controller.manageAccountDashboardController.sessionCurrent != null && controller.manageAccountDashboardController.accountId.value != null) { controller.manageAccountDashboardController.logout( + context: context, session: controller.manageAccountDashboardController.sessionCurrent!, accountId: controller.manageAccountDashboardController.accountId.value! ); diff --git a/lib/features/manage_account/presentation/menu/settings/settings_view.dart b/lib/features/manage_account/presentation/menu/settings/settings_view.dart index 7144031044..0d4c5a64df 100644 --- a/lib/features/manage_account/presentation/menu/settings/settings_view.dart +++ b/lib/features/manage_account/presentation/menu/settings/settings_view.dart @@ -103,6 +103,7 @@ class SettingsView extends GetWidget { overflow: CommonTextStyle.defaultTextOverFlow, style: Theme.of(context).textTheme.bodyLarge?.copyWith( fontSize: 20, + fontWeight: FontWeight.bold, color: Colors.black ) ) diff --git a/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart b/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart index 512d447434..c781d627ef 100644 --- a/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart +++ b/lib/features/manage_account/presentation/menu/widgets/account_profile_widget.dart @@ -50,7 +50,10 @@ class AccountProfileWidget extends StatelessWidget { padding: const EdgeInsetsDirectional.only(start: 16), child: TextOverflowBuilder( userProfile?.email ?? '', - style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.black) + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ) ), ), TMailButtonWidget( diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/delete_identity_dialog_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/delete_identity_dialog_builder.dart index a6e6e9fe34..9c14c48be6 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/delete_identity_dialog_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/delete_identity_dialog_builder.dart @@ -26,7 +26,7 @@ class DeleteIdentityDialogBuilder extends StatelessWidget { responsiveUtils: responsiveUtils, mobile: (_buildDeleteDialog(context) ..alignment(Alignment.bottomCenter) - ..outsideDialogPadding(const EdgeInsets.only(left: 0, right: 0, bottom: PlatformInfo.isWeb ? 42 : 16)) + ..outsideDialogPadding(const EdgeInsets.only(bottom: PlatformInfo.isWeb ? 42 : 16)) ..widthDialog(MediaQuery.of(context).size.width - 16) ..heightDialog(280)) .build(), diff --git a/lib/features/sending_queue/presentation/sending_queue_controller.dart b/lib/features/sending_queue/presentation/sending_queue_controller.dart index 6eed34be2b..6be065d9d5 100644 --- a/lib/features/sending_queue/presentation/sending_queue_controller.dart +++ b/lib/features/sending_queue/presentation/sending_queue_controller.dart @@ -202,9 +202,9 @@ class SendingQueueController extends BaseController with MessageDialogActionMixi void _deleteListSendingEmailAction(BuildContext context, List listSendingEmails) { showConfirmDialogAction( - context, - AppLocalizations.of(context).messageDialogDeleteSendingEmail, - AppLocalizations.of(currentContext!).delete, + context: context, + message: AppLocalizations.of(context).messageDialogDeleteSendingEmail, + actionName: AppLocalizations.of(currentContext!).delete, title: AppLocalizations.of(currentContext!).deleteOfflineEmail, icon: SvgPicture.asset(imagePaths.icDeleteDialogRecipients), alignCenter: true, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c69626d619..d77e50ea4a 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-01-16T12:04:11.990592", + "@@last_modified": "2024-01-16T15:38:46.103746", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3709,5 +3709,23 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "logoutConfirmation": "Logout Confirmation", + "@logoutConfirmation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "yesLogout": "Yes, Log out", + "@yesLogout": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageConfirmationLogout": "Do you want to log out of", + "@messageConfirmationLogout": { + "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 9951e6a4b2..9bb13c704d 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3864,4 +3864,22 @@ class AppLocalizations { 'Account settings', name: 'accountSettings'); } + + String get logoutConfirmation { + return Intl.message( + 'Logout Confirmation', + name: 'logoutConfirmation'); + } + + String get yesLogout { + return Intl.message( + 'Yes, Log out', + name: 'yesLogout'); + } + + String get messageConfirmationLogout { + return Intl.message( + 'Do you want to log out of', + name: 'messageConfirmationLogout'); + } } \ No newline at end of file diff --git a/lib/main/utils/authenticated_account_manager.dart b/lib/main/utils/authenticated_account_manager.dart index ab36ca5d03..e8eba78d73 100644 --- a/lib/main/utils/authenticated_account_manager.dart +++ b/lib/main/utils/authenticated_account_manager.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/string_extension.dart'; @@ -125,8 +127,8 @@ class AuthenticatedAccountManager { ) ] ), - accountNameStyle: Theme.of(context).textTheme.bodyLarge!.copyWith( - color: LinagoraSysColors.material().onSurface, + accountNameStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: LinagoraSysColors.material().onSurface ), accountIdStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( color: LinagoraRefColors.material().tertiary[20],