diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart index f214796c8..5a6d76c11 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart @@ -21,7 +21,7 @@ class ManageAccountDashBoardController extends ReloadableController { final GetUserProfileInteractor _getUserProfileInteractor; - final menuDrawerKey = GlobalKey(); + final menuDrawerKey = GlobalKey(debugLabel: 'manage_account'); final appInformation = Rxn(); final userProfile = Rxn(); diff --git a/lib/features/manage_account/presentation/profiles/identities/identities_bindings.dart b/lib/features/manage_account/presentation/profiles/identities/identities_bindings.dart index 42fd28c44..41c6cd728 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_bindings.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_bindings.dart @@ -25,9 +25,9 @@ class IdentitiesBindings extends BaseBindings { void bindingsController() { Get.lazyPut(() => IdentitiesController( Get.find(), + Get.find(), Get.find(), Get.find(), - Get.find(), )); } diff --git a/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart b/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart index 7aa0f605e..30fe64e6a 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart @@ -1,5 +1,6 @@ import 'package:core/core.dart'; +import 'package:model/model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -20,8 +21,6 @@ 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:uuid/uuid.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class IdentitiesController extends BaseController { @@ -29,8 +28,6 @@ class IdentitiesController extends BaseController { final _appToast = Get.find(); final _imagePaths = Get.find(); final _responsiveUtils = Get.find(); - final _imagePaths = Get.find(); - final _appToast = Get.find(); final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; final CreateNewIdentityInteractor _createNewIdentityInteractor; @@ -38,14 +35,14 @@ class IdentitiesController extends BaseController { final DeleteIdentityInteractor _deleteIdentityInteractor; final identitySelected = Rxn(); - final listIdentities = [].obs; + final listAllIdentities = [].obs; + final listSelectedIdentities = [].obs; + + final idIdentityAll = IdentityId(Id('all')); IdentitiesController( this._getAllIdentitiesInteractor, this._deleteIdentityInteractor, - ); - IdentitiesController( - this._getAllIdentitiesInteractor, this._createNewIdentityInteractor, this._uuid, ); @@ -67,7 +64,10 @@ class IdentitiesController extends BaseController { (success) { if (success is GetAllIdentitiesSuccess) { if (success.identities?.isNotEmpty == true) { - listIdentities.value = success.identities ?? []; + _addNewIdentityAsAll(); + final newListIdentities = success.identities!; + newListIdentities.sortByMayDelete(); + listAllIdentities.addAll(newListIdentities); setIdentityDefault(); } } else if (success is CreateNewIdentitySuccess) { @@ -96,17 +96,47 @@ class IdentitiesController extends BaseController { consumeState(_getAllIdentitiesInteractor.execute(accountId)); } + void _refreshAllIdentities() { + identitySelected.value = null; + listAllIdentities.clear(); + listSelectedIdentities.clear(); + + final accountId = _accountDashBoardController.accountId.value; + if (accountId != null) { + _getAllIdentities(accountId); + } + } + + void _addNewIdentityAsAll() { + if (currentContext != null) { + listAllIdentities.add(Identity( + id: idIdentityAll, + name: AppLocalizations.of(currentContext!).all_identities)); + } + } + void setIdentityDefault() { try { - final identityDefault = listIdentities.firstWhere((identity) => identity.mayDelete == false); + final identityDefault = listAllIdentities.firstWhere((identity) => identity.mayDelete == false); selectIdentity(identityDefault); } catch (exception) { - selectIdentity(listIdentities.first); + selectIdentity(listAllIdentities.first); } } void selectIdentity(Identity? newIdentity) { identitySelected.value = newIdentity; + if (newIdentity != null) { + _updateListSelectedIdentities(newIdentity); + } + } + + void _updateListSelectedIdentities(Identity newIdentity) { + if (newIdentity.id == idIdentityAll) { + listSelectedIdentities.value = listAllIdentities.sublist(1); + } else { + listSelectedIdentities.value = [newIdentity]; + } } void goToCreateNewIdentity() async { @@ -138,10 +168,7 @@ class IdentitiesController extends BaseController { icon: _imagePaths.icSelected); } - final accountId = _accountDashBoardController.accountId.value; - if (accountId != null) { - _getAllIdentities(accountId); - } + _refreshAllIdentities(); } void openConfirmationDialogDeleteIdentityAction(BuildContext context, Identity identity) { @@ -209,10 +236,7 @@ class IdentitiesController extends BaseController { icon: _imagePaths.icDeleteToast); } - final accountId = _accountDashBoardController.accountId.value; - if (accountId != null) { - _getAllIdentities(accountId); - } + _refreshAllIdentities(); } void _deleteIdentityFailure(DeleteIdentityFailure failure) { diff --git a/lib/features/manage_account/presentation/profiles/identities/identities_view.dart b/lib/features/manage_account/presentation/profiles/identities/identities_view.dart index ff87e9416..6a070f044 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_view.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_view.dart @@ -2,16 +2,18 @@ import 'package:core/core.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/identities_controller.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/widgets/identity_bottom_sheet_action_tile_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/widgets/identity_info_tile_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -class IdentitiesView extends GetWidget with PopupMenuWidgetMixin { +class IdentitiesView extends GetWidget with PopupMenuWidgetMixin, AppLoaderMixin { final _responsiveUtils = Get.find(); final _imagePaths = Get.find(); @@ -30,17 +32,17 @@ class IdentitiesView extends GetWidget with PopupMenuWidge controller.identitySelected.value?.name ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black), maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis, )), ], ), - items: controller.listIdentities.map((item) => DropdownMenuItem( + items: controller.listAllIdentities.map((item) => DropdownMenuItem( value: item, child: Text( item.name ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black), maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis, ), )).toList(), value: controller.identitySelected.value, @@ -68,7 +70,9 @@ class IdentitiesView extends GetWidget with PopupMenuWidge if (!_responsiveUtils.isMobile(context)) (ButtonBuilder(_imagePaths.icAddIdentity) ..key(const Key('button_new_identity')) - ..decoration(BoxDecoration(borderRadius: BorderRadius.circular(10), color: AppColor.colorTextButton)) + ..decoration(BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: AppColor.colorTextButton)) ..paddingIcon(const EdgeInsets.only(right: 8)) ..iconColor(Colors.white) ..maxWidth(170) @@ -86,16 +90,27 @@ class IdentitiesView extends GetWidget with PopupMenuWidge mobile: Scaffold( body: Container( margin: const EdgeInsets.only(top: 16, bottom: 16, right: 24), - child: SingleChildScrollView(child: Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buttonSelectIdentity, - Obx(() => IdentityInfoTileBuilder(_imagePaths, _responsiveUtils, - controller.identitySelected.value, - onMenuItemIdentityAction: (identity, position) => - _openIdentityMenuAction(context, identity, position))), + const SizedBox(height: 24), + _buildLoadingView(), + Expanded(child: Obx(() => MasonryGridView.count( + key: const Key('list_identities'), + crossAxisSpacing: 24.0, + mainAxisSpacing: 24.0, + crossAxisCount: _responsiveUtils.isDesktop(context) || _responsiveUtils.isTabletLarge(context) + ? 2 : 1, + itemCount: controller.listSelectedIdentities.length, + itemBuilder: (context, index) => + IdentityInfoTileBuilder(_imagePaths, _responsiveUtils, + controller.listSelectedIdentities[index], + onMenuItemIdentityAction: (identity, position) => + _openIdentityMenuAction(context, identity, position)) + ))) ] - )) + ) ), floatingActionButton: _responsiveUtils.isMobile(context) ? FloatingActionButton( @@ -106,23 +121,44 @@ class IdentitiesView extends GetWidget with PopupMenuWidge : null), desktop: Container( margin: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), - child: SingleChildScrollView(child: Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: constraints.maxWidth / 2, child: buttonSelectIdentity), - Obx(() => IdentityInfoTileBuilder(_imagePaths, _responsiveUtils, - controller.identitySelected.value, - maxWidth: constraints.maxWidth / 2, - onMenuItemIdentityAction: (identity, position) => - _openIdentityMenuAction(context, identity, position))), + const SizedBox(height: 24), + _buildLoadingView(), + Expanded(child: Obx(() => MasonryGridView.count( + key: const Key('list_identities'), + crossAxisSpacing: 24.0, + mainAxisSpacing: 24.0, + crossAxisCount: _responsiveUtils.isDesktop(context) || _responsiveUtils.isTabletLarge(context) + ? 2 : 1, + itemCount: controller.listSelectedIdentities.length, + itemBuilder: (context, index) => + IdentityInfoTileBuilder(_imagePaths, _responsiveUtils, + controller.listSelectedIdentities[index], + onMenuItemIdentityAction: (identity, position) => + _openIdentityMenuAction(context, identity, position)) + ))) ] - )) + ) ) )); } + Widget _buildLoadingView() { + return Obx(() => controller.viewState.value.fold( + (failure) => const SizedBox.shrink(), + (success) => success is LoadingState + ? Padding( + padding: const EdgeInsets.only(bottom: 24), + child: loadingWidget) + : const SizedBox.shrink() + )); + } + void _openIdentityMenuAction(BuildContext context, Identity identity, RelativeRect? position) { if (_responsiveUtils.isScreenWithShortestSide(context)) { diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/identity_info_tile_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/identity_info_tile_builder.dart index dfa50b5b2..fb5b9bcc5 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/identity_info_tile_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/identity_info_tile_builder.dart @@ -12,7 +12,6 @@ class IdentityInfoTileBuilder extends StatelessWidget { final ImagePaths _imagePaths; final ResponsiveUtils _responsiveUtils; final Identity? _identity; - final double? maxWidth; final OnMenuItemIdentityAction? onMenuItemIdentityAction; const IdentityInfoTileBuilder( @@ -21,7 +20,6 @@ class IdentityInfoTileBuilder extends StatelessWidget { this._identity, { Key? key, - this.maxWidth, this.onMenuItemIdentityAction, } ) : super(key: key); @@ -39,14 +37,31 @@ class IdentityInfoTileBuilder extends StatelessWidget { border: Border.all(color: AppColor.colorBorderIdentityInfo, width: 1), color: Colors.white), padding: const EdgeInsets.only(top: 12, bottom: 12), - margin: const EdgeInsets.only(top: 12), - width: maxWidth, child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.only(left: 12, right: 8), child: Row(children: [ - Expanded(child: Text(_identity?.name ?? '', - style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 17, color: Colors.black))), + if (_identity?.mayDelete == false) + Expanded(child: Row(children: [ + Text(_identity?.name ?? '', + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: Colors.black)), + const SizedBox(width: 10), + Expanded(child: Text( + '(${AppLocalizations.of(context).default_value})', + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 15, + color: AppColor.colorHintSearchBar))) + ])) + else + Expanded(child: Text(_identity?.name ?? '', + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: Colors.black)),), buildIconWebHasPosition( context, icon: SvgPicture.asset(_imagePaths.icMoreVertical, fit: BoxFit.fill), @@ -80,7 +95,8 @@ class IdentityInfoTileBuilder extends StatelessWidget { Expanded(child: Text(_identity?.email ?? '', style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: AppColor.colorHintSearchBar))) ])), - if (_identity?.replyTo != null && _identity?.replyTo?.isNotEmpty == true) const SizedBox(height: 10), + if (_identity?.replyTo != null && _identity?.replyTo?.isNotEmpty == true) + const SizedBox(height: 10), if (_identity?.replyTo != null && _identity?.replyTo?.isNotEmpty == true) Padding( padding: const EdgeInsets.symmetric(horizontal: 12), @@ -92,9 +108,10 @@ class IdentityInfoTileBuilder extends StatelessWidget { Expanded(child: Text(_identity?.replyTo?.listEmailAddressToString(isFullEmailAddress: true) ?? '', style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: AppColor.colorHintSearchBar))) ])), - if (_identity?.bcc != null && _identity?.bcc?.isNotEmpty == true) const SizedBox(height: 6), if (_identity?.bcc != null && _identity?.bcc?.isNotEmpty == true) - Padding( + const SizedBox(height: 6), + if (_identity?.bcc != null && _identity?.bcc?.isNotEmpty == true) + Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row(children: [ Container( diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index e8f8ef156..bcbc99991 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1227,6 +1227,19 @@ class AppLocalizations { name: 'you_have_created_a_new_identity'); } + String get all_identities { + return Intl.message( + 'All identities', + name: 'all_identities'); + } + + String get default_value { + return Intl.message( + 'Default', + name: 'default_value', + ); + } + String get delete_identity { return Intl.message('Delete identity', name: 'delete_identity'); diff --git a/model/lib/extensions/list_identities_extension.dart b/model/lib/extensions/list_identities_extension.dart new file mode 100644 index 000000000..9329a04b1 --- /dev/null +++ b/model/lib/extensions/list_identities_extension.dart @@ -0,0 +1,17 @@ + +import 'package:jmap_dart_client/jmap/identities/identity.dart'; + +extension ListIdentitiesExtension on List { + + void sortByMayDelete() { + sort((identity1, identity2) { + if (identity1.mayDelete == false && identity2.mayDelete == true) { + return -1; + } if (identity1.mayDelete == true && identity2.mayDelete == false) { + return 1; + } else { + return 0; + } + }); + } +} \ No newline at end of file diff --git a/model/lib/model.dart b/model/lib/model.dart index fb1275c0b..a50139992 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -55,6 +55,7 @@ export 'extensions/media_type_extension.dart'; export 'extensions/list_attachment_extension.dart'; export 'extensions/list_presentation_email_extension.dart'; export 'extensions/list_email_content_extension.dart'; +export 'extensions/list_identities_extension.dart'; // Download export 'download/download_task_id.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index c1521ff06..2693685b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -146,6 +146,8 @@ dependencies: dropdown_button2: 1.4.0 + flutter_staggered_grid_view: 0.6.1 + dev_dependencies: flutter_test: sdk: flutter