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/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/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 e62d020a99..b697145b7f 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -211,6 +211,8 @@ 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 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 83058a04af..15c909bcf5 100644
--- a/core/lib/presentation/utils/theme_utils.dart
+++ b/core/lib/presentation/utils/theme_utils.dart
@@ -22,21 +22,21 @@ 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(
+ fontSize: 17
+ ),
bodyMedium: TextStyle(
fontWeight: FontWeight.w500,
+ fontSize: 15
),
bodySmall: TextStyle(
- fontWeight: FontWeight.w500
+ fontWeight: FontWeight.normal,
+ fontSize: 15
),
headlineLarge: TextStyle(
fontWeight: FontWeight.w600,
@@ -44,6 +44,10 @@ class ThemeUtils {
),
labelSmall: TextStyle(
fontWeight: FontWeight.normal
+ ),
+ labelLarge: TextStyle(
+ fontWeight: FontWeight.w500,
+ fontSize: 16
)
);
}
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/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/base/base_controller.dart b/lib/features/base/base_controller.dart
index 581a0a45e1..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';
@@ -25,13 +26,17 @@ 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_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';
+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';
@@ -40,7 +45,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';
@@ -58,6 +62,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
@@ -75,6 +80,8 @@ abstract class BaseController extends GetxController
final ResponsiveUtils responsiveUtils = Get.find();
final Uuid uuid = Get.find();
final AppStore appStore = Get.find();
+ final SetCurrentActiveAccountInteractor _setCurrentActiveAccountInteractor = Get.find();
+ final AuthenticatedAccountManager authenticatedAccountManager = Get.find();
final _fcmReceiver = FcmReceiver.instance;
bool _isFcmEnabled = false;
@@ -173,7 +180,7 @@ abstract class BaseController extends GetxController
return;
}
if (exception is BadCredentialsException || exception is ConnectionError) {
- clearDataAndGoToLoginPage();
+ _handleBadCredentials();
}
}
@@ -182,19 +189,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);
}
}
@@ -274,20 +280,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() {
@@ -302,48 +297,109 @@ 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 {
_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();
}
@@ -352,7 +408,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) {
@@ -361,14 +432,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) {
@@ -379,10 +460,90 @@ 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();
+ }
+ }
+ }
+
+ 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 setCurrentActiveAccount(PersonalAccount activeAccount) {
+ consumeState(_setCurrentActiveAccountInteractor.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/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/base/reloadable/reloadable_controller.dart b/lib/features/base/reloadable/reloadable_controller.dart
index fc00649c3c..b9109e26a4 100644
--- a/lib/features/base/reloadable/reloadable_controller.dart
+++ b/lib/features/base/reloadable/reloadable_controller.dart
@@ -1,31 +1,34 @@
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';
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/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/authenticated_account_manager.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) {
@@ -33,13 +36,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));
+ }
}
}
@@ -68,38 +69,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
));
@@ -112,7 +83,7 @@ abstract class ReloadableController extends BaseController {
MessageToastUtils.getMessageByException(currentContext!, exception) ?? AppLocalizations.of(currentContext!).unknownError
);
}
- clearDataAndGoToLoginPage();
+ clearAllDataAndBackToLogin();
}
void _handleGetSessionSuccess(GetSessionSuccess success) {
@@ -121,19 +92,65 @@ 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();
+ clearAllDataAndBackToLogin();
}
}
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
+ ));
+ }
+
+ void switchActiveAccount({
+ required PersonalAccount currentAccount,
+ required PersonalAccount nextAccount,
+ required Session sessionCurrentAccount,
+ }) async {
+ await pushAndPopAll(
+ AppRoutes.home,
+ arguments: LoginNavigateArguments(
+ navigateType: LoginNavigateType.switchActiveAccount,
+ currentAccount: currentAccount,
+ sessionCurrentAccount: sessionCurrentAccount,
+ 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/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 ffe3b306c9..dae83d2068 100644
--- a/lib/features/caching/caching_manager.dart
+++ b/lib/features/caching/caching_manager.dart
@@ -2,7 +2,9 @@ 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: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,27 +82,30 @@ 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);
}
- 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);
}
@@ -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/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/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/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/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/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart
index e3b3d7b788..f093348600 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,8 +25,15 @@ 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/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';
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';
@@ -45,8 +55,8 @@ class HomeController extends ReloadableController {
this._cleanupRecentLoginUsernameCacheInteractor,
);
- PersonalAccount? currentAccount;
EmailId? _emailIdPreview;
+ StreamSubscription? _sessionStreamSubscription;
@override
void onInit() {
@@ -68,6 +78,12 @@ class HomeController extends ReloadableController {
super.onReady();
}
+ @override
+ void onClose() {
+ _sessionStreamSubscription?.cancel();
+ super.onClose();
+ }
+
@override
void handleReloaded(Session session) {
if (_emailIdPreview != null) {
@@ -87,20 +103,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 +173,174 @@ class HomeController extends ReloadableController {
}
}
}
+
+ void _handleLoginNavigateArguments(LoginNavigateArguments navigateArguments) async {
+ switch (navigateArguments.navigateType) {
+ case LoginNavigateType.switchActiveAccount:
+ _switchActiveAccount(
+ navigateArguments.currentAccount!,
+ navigateArguments.sessionCurrentAccount!,
+ navigateArguments.nextActiveAccount!);
+ break;
+ case LoginNavigateType.selectActiveAccount:
+ _showListAccountPicker();
+ break;
+ default:
+ await _cleanupCache();
+ break;
+ }
+ }
+
+ void _switchActiveAccount(
+ PersonalAccount currentAccount,
+ Session sessionCurrentAccount,
+ PersonalAccount nextAccount
+ ) {
+ setUpInterceptors(nextAccount);
+
+ _sessionStreamSubscription = getSessionInteractor.execute(
+ accountId: nextAccount.accountId,
+ userName: nextAccount.userName
+ ).listen(
+ (viewState) {
+ viewState.fold(
+ (failure) => _handleGetSessionFailureWhenSwitchActiveAccount(
+ currentActiveAccount: currentAccount,
+ session: sessionCurrentAccount,
+ exception: failure),
+ (success) => success is GetSessionSuccess
+ ? _handleGetSessionSuccessWhenSwitchActiveAccount(
+ currentAccount,
+ nextAccount,
+ sessionCurrentAccount,
+ success.session)
+ : null,
+ );
+ },
+ onError: (error, stack) {
+ logError('HomeController::_switchActiveAccount:Exception: $error | Stack: $stack');
+ _handleGetSessionFailureWhenSwitchActiveAccount(
+ currentActiveAccount: currentAccount,
+ session: sessionCurrentAccount,
+ exception: error);
+ }
+ );
+ }
+
+ void _handleGetSessionSuccessWhenSwitchActiveAccount(
+ PersonalAccount currentAccount,
+ PersonalAccount nextAccount,
+ Session sessionCurrentAccount,
+ Session sessionNextAccount,
+ ) async {
+ log('HomeController::_handleGetSessionSuccessWhenSwitchActiveAccount:sessionNextAccount: $sessionNextAccount');
+ await popAndPush(
+ RouteUtils.generateNavigationRoute(AppRoutes.dashboard),
+ arguments: SwitchActiveAccountArguments(
+ sessionCurrentAccount: sessionCurrentAccount,
+ sessionNextAccount: sessionNextAccount,
+ currentAccount: currentAccount,
+ nextAccount: nextAccount,
+ )
+ );
+ }
+
+ void _restoreActiveAccount({
+ required PersonalAccount currentActiveAccount,
+ required Session session,
+ dynamic exception
+ }) async {
+ logError('HomeController::_restoreActiveAccount:currentActiveAccount: $currentActiveAccount');
+ await popAndPush(
+ RouteUtils.generateNavigationRoute(AppRoutes.dashboard),
+ arguments: RestoreActiveAccountArguments(
+ currentAccount: currentActiveAccount,
+ 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');
+ return;
+ }
+
+ authenticatedAccountManager.showAccountsBottomSheetModal(
+ context: currentContext!,
+ onSelectActiveAccountAction: _handleSelectActiveAccount,
+ onAddAnotherAccountAction: (_) => _handleAddOtherAccount()
+ );
+ }
+
+ 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: SelectActiveAccountArguments(
+ session: sessionActiveAccount,
+ activeAccount: 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();
+ }
+
+ void _handleAddOtherAccount() {
+ navigateToTwakeIdPage();
+ }
}
\ 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 1f145075bd..a1b0f8beef 100644
--- a/lib/features/login/data/datasource/account_datasource.dart
+++ b/lib/features/login/data/datasource/account_datasource.dart
@@ -6,4 +6,8 @@ abstract class AccountDatasource {
Future setCurrentAccount(PersonalAccount newCurrentAccount);
Future deleteCurrentAccount(String accountId);
+
+ Future> getAllAccount();
+
+ 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 d6c1606976..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
@@ -41,4 +41,18 @@ 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);
+ }
+
+ @override
+ Future setCurrentActiveAccount(PersonalAccount activeAccount) {
+ return Future.sync(() async {
+ return await _accountCacheManager.setCurrentActiveAccount(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 3d3d6a43e4..7135fa7cae 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,28 @@ 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();
+ }
+ }
+
+ Future setCurrentActiveAccount(PersonalAccount activeAccount) async {
+ log('AccountCacheManager::setCurrentActiveAccount(): $activeAccount');
+ final newAccountCache = activeAccount.toCache(isSelected: true);
+ final allAccounts = await _accountCacheClient.getAll();
+ log('AccountCacheManager::setCurrentActiveAccount::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 0df1f5dedc..e9a35af0ad 100644
--- a/lib/features/login/data/repository/account_repository_impl.dart
+++ b/lib/features/login/data/repository/account_repository_impl.dart
@@ -22,4 +22,14 @@ class AccountRepositoryImpl extends AccountRepository {
Future deleteCurrentAccount(String hashId) {
return _accountDatasource.deleteCurrentAccount(hashId);
}
+
+ @override
+ Future> getAllAccount() {
+ return _accountDatasource.getAllAccount();
+ }
+
+ @override
+ Future setCurrentActiveAccount(PersonalAccount activeAccount) {
+ return _accountDatasource.setCurrentActiveAccount(activeAccount);
+ }
}
\ 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..7c957a5cae 100644
--- a/lib/features/login/domain/repository/account_repository.dart
+++ b/lib/features/login/domain/repository/account_repository.dart
@@ -7,4 +7,8 @@ abstract class AccountRepository {
Future setCurrentAccount(PersonalAccount newCurrentAccount);
Future deleteCurrentAccount(String hashId);
+
+ Future> getAllAccount();
+
+ Future setCurrentActiveAccount(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/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