diff --git a/assets/images/ic_delete_outline.svg b/assets/images/ic_delete_outline.svg deleted file mode 100644 index 214e3a8d8..000000000 --- a/assets/images/ic_delete_outline.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index d22cddd97..6ded7869e 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -169,6 +169,7 @@ extension AppColor on Color { static const colorProgressQuotasWarning = Color(0xFFFFA000); static const colorOutOfStorageQuotasWarning = Color(0xffE64646); static const colorThumbScrollBar = Color(0xFFAEB7C2); + static const colorCreateNewIdentityButton = Color(0xFFEBEDF0); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 7d9e3d0ee..de00cbe38 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -26,7 +26,6 @@ class ImagePaths { String get icSelected => _getImagePath('ic_selected.svg'); String get icUnSelected => _getImagePath('ic_unselected.svg'); String get icDelete => _getImagePath('ic_delete.svg'); - String get icDeleteOutline => _getImagePath('ic_delete_outline.svg'); String get icSpam => _getImagePath('ic_spam.svg'); String get icMove => _getImagePath('ic_move.svg'); String get icReadToast => _getImagePath('ic_read_toast.svg'); 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 ec636ec43..0f03397fa 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 @@ -36,6 +36,7 @@ class SettingsFirstLevelView extends GetWidget { SettingFirstLevelTileBuilder( AppLocalizations.of(context).profiles, AccountMenuItem.profiles.getIcon(_imagePaths), + subtitle: AppLocalizations.of(context).profilesSettingExplanation, () => controller.selectSettings(AccountMenuItem.profiles) ), Divider( 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 9121eef8b..b2b11cebd 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart @@ -43,7 +43,6 @@ class IdentitiesController extends BaseController { final DeleteIdentityInteractor _deleteIdentityInteractor; final EditIdentityInteractor _editIdentityInteractor; - final selectedIndex = Rxn(); final identitySelected = Rxn(); final listAllIdentities = [].obs; @@ -71,28 +70,22 @@ class IdentitiesController extends BaseController { @override void onDone() { viewState.value.fold( - (failure) { - if (failure is DeleteIdentityFailure) { - _deleteIdentityFailure(failure); - } - }, - (success) { - if (success is GetAllIdentitiesSuccess) { - if (success.identities?.isNotEmpty == true) { - final newListIdentities = success.identities! - .where((identity) => identity.mayDelete == true) - .toList(); - listAllIdentities.addAll(newListIdentities); - selectIdentity(0); - } - } else if (success is CreateNewIdentitySuccess) { - _createNewIdentitySuccess(success); - } else if (success is DeleteIdentitySuccess) { - _deleteIdentitySuccess(success); - } else if (success is EditIdentitySuccess) { - _editIdentitySuccess(success); - } + (failure) { + if (failure is DeleteIdentityFailure) { + _deleteIdentityFailure(failure); } + }, + (success) { + if (success is GetAllIdentitiesSuccess) { + _handleGetAllIdentitiesSuccess(success); + } else if (success is CreateNewIdentitySuccess) { + _createNewIdentitySuccess(success); + } else if (success is DeleteIdentitySuccess) { + _deleteIdentitySuccess(success); + } else if (success is EditIdentitySuccess) { + _editIdentitySuccess(success); + } + } ); } @@ -117,7 +110,7 @@ class IdentitiesController extends BaseController { } void _refreshAllIdentities() { - selectedIndex.value = null; + identitySelected.value = null; listAllIdentities.clear(); final accountId = _accountDashBoardController.accountId.value; @@ -126,11 +119,21 @@ class IdentitiesController extends BaseController { } } - void selectIdentity(int? index) { - selectedIndex.value = index; - if (selectedIndex.value != null) { - identitySelected.value = listAllIdentities[selectedIndex.value!]; + void _handleGetAllIdentitiesSuccess(GetAllIdentitiesSuccess success) { + if (success.identities?.isNotEmpty == true) { + final newListIdentities = success.identities! + .where((identity) => identity.mayDelete == true) + .toList(); + listAllIdentities.addAll(newListIdentities); } + + if (listAllIdentities.isNotEmpty) { + selectIdentity(listAllIdentities.first); + } + } + + void selectIdentity(Identity? newIdentity) { + identitySelected.value = newIdentity; } void goToCreateNewIdentity(BuildContext context) async { @@ -226,6 +229,8 @@ class IdentitiesController extends BaseController { } void _deleteIdentityAction(Identity identity) { + popBack(); + final accountId = _accountDashBoardController.accountId.value; if (accountId != null && identity.id != null) { consumeState(_deleteIdentityInteractor.execute(accountId, identity.id!)); @@ -319,7 +324,5 @@ class IdentitiesController extends BaseController { ImagePaths get imagePaths => _imagePaths; - bool isSignatureShow() { - return selectedIndex.value!= null && selectedIndex.value! >= 0; - } + bool get isSignatureShow => identitySelected.value != null; } \ No newline at end of file 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 153c9ab50..396758c92 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_view.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_view.dart @@ -1,12 +1,12 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.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/identities_header_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder_web.dart' as identities_listview_web; class IdentitiesView extends GetWidget with PopupMenuWidgetMixin, AppLoaderMixin { @@ -17,106 +17,52 @@ class IdentitiesView extends GetWidget with PopupMenuWidge @override Widget build(BuildContext context) { - return ResponsiveWidget( - responsiveUtils: _responsiveUtils, - mobile: _buildIdentitiesViewMobile(context), - tablet: _buildIdentitiesViewMobile(context), - desktop: _buildIdentitiesViewWeb(context), - tabletLarge: _buildIdentitiesViewMobile(context), + return Container( + color: Colors.transparent, + margin: const EdgeInsets.all(24), + child: _responsiveUtils.isWebDesktop(context) + ? _buildIdentitiesViewWebDesktop(context) + : _buildIdentitiesViewMobile(context), ); } Widget _buildIdentitiesViewMobile(BuildContext context) { - return SingleChildScrollView( - padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 24.0, bottom: 24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for(Widget item in _buildIdentitiesTitles(context)) - item, - _buildCreateIdentityButton(context), - IdentitiesRadioListBuilder(controller: controller), - ], - ), - ); - } - - Widget _buildIdentitiesViewWeb(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 236, - padding: const EdgeInsets.only(right: 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for(Widget widget in _buildIdentitiesTitles(context)) - widget, - _buildCreateIdentityButton(context), - ], - ), - ), - Expanded( - child: identities_listview_web.IdentitiesRadioListBuilder(controller: controller), - ), - ], - ), - ); - } - - List _buildIdentitiesTitles(BuildContext context) { - return [ - Text( - AppLocalizations.of(context).identities.inCaps, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 17, - color: Colors.black)), - const SizedBox(height: 4.0), - Text( - AppLocalizations.of(context).identities_description, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: AppColor.colorSettingExplanation), - ), - const SizedBox(height: 12.0), - ]; - } - - Widget _buildCreateIdentityButton(BuildContext context) { - return Row( + return Column( children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: (ButtonBuilder(_imagePaths.icAddIdentity) - ..key(const Key('button_add_identity')) - ..decoration(BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: AppColor.colorBorderIdentityInfo)) - ..paddingIcon(const EdgeInsets.only(right: 12)) - ..iconColor(AppColor.primaryColor) - ..size(28) - ..padding(const EdgeInsets.symmetric(vertical: 12)) - ..textStyle(const TextStyle( - fontSize: 16, - color: AppColor.primaryColor, - fontWeight: FontWeight.w500, - )) - ..onPressActionClick(() => controller.goToCreateNewIdentity(context)) - ..text( - AppLocalizations.of(context).create_new_identity, - isVertical: false, - )) - .build(), - ), + IdentitiesHeaderWidget( + imagePaths: _imagePaths, + responsiveUtils: _responsiveUtils, + onAddNewIdentityAction: () => controller.goToCreateNewIdentity(context), ), + const SizedBox(height: 12), + IdentitiesRadioListBuilder( + controller: controller, + responsiveUtils: _responsiveUtils, + imagePaths: _imagePaths + ) ], ); } + Widget _buildIdentitiesViewWebDesktop(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 224, + child: IdentitiesHeaderWidget( + imagePaths: _imagePaths, + responsiveUtils: _responsiveUtils, + onAddNewIdentityAction: () => controller.goToCreateNewIdentity(context), + ) + ), + const SizedBox(width: 12), + Expanded(child: IdentitiesRadioListBuilder( + controller: controller, + responsiveUtils: _responsiveUtils, + imagePaths: _imagePaths + )), + ], + ); + } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/identities_header_widget.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/identities_header_widget.dart new file mode 100644 index 000000000..6d581de4c --- /dev/null +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/identities_header_widget.dart @@ -0,0 +1,62 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/views/button/button_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnAddNewIdentityAction = Function(); + +class IdentitiesHeaderWidget extends StatelessWidget { + + const IdentitiesHeaderWidget({ + Key? key, + required this.imagePaths, + required this.responsiveUtils, + this.onAddNewIdentityAction, + }) : super(key: key); + + final ImagePaths imagePaths; + final ResponsiveUtils responsiveUtils; + final OnAddNewIdentityAction? onAddNewIdentityAction; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + AppLocalizations.of(context).identities, + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: Colors.black)), + const SizedBox(height: 4), + Text( + AppLocalizations.of(context).identitiesSettingExplanation, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + color: AppColor.colorSettingExplanation)), + const SizedBox(height: 24), + (ButtonBuilder(imagePaths.icAddIdentity) + ..key(const Key('button_add_identity')) + ..decoration(BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: AppColor.colorCreateNewIdentityButton)) + ..paddingIcon(const EdgeInsets.only(right: 8)) + ..iconColor(AppColor.colorTextButton) + ..size(28) + ..radiusSplash(12) + ..padding(const EdgeInsets.symmetric(vertical: 10)) + ..textStyle(const TextStyle( + fontSize: 16, + color: AppColor.colorTextButton, + fontWeight: FontWeight.w500)) + ..onPressActionClick(() => onAddNewIdentityAction?.call()) + ..text(AppLocalizations.of(context).createNewIdentity, isVertical: false) + ).build() + ]), + ); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart index 5b19dab4d..daba4d054 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart @@ -1,79 +1,105 @@ import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:fading_edge_scrollview/fading_edge_scrollview.dart'; import 'package:flutter/material.dart'; -import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; +import 'package:get/get_state_manager/get_state_manager.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_list_tile_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart'; -import 'identity_list_tile_builder.dart'; - -class IdentitiesRadioListBuilder extends StatefulWidget { - const IdentitiesRadioListBuilder({ - Key? key, - required this.controller, - }) : super(key: key); +class IdentitiesRadioListBuilder extends StatelessWidget { final IdentitiesController controller; + final ResponsiveUtils responsiveUtils; + final ImagePaths imagePaths; - @override - State createState() => _IdentitiesRadioListBuilderState(); -} - -class _IdentitiesRadioListBuilderState extends State { - late final controller = widget.controller; + const IdentitiesRadioListBuilder({ + Key? key, + required this.controller, + required this.responsiveUtils, + required this.imagePaths, + }) : super(key: key); @override Widget build(BuildContext context) { - return Obx(() { - if (controller.listAllIdentities.isEmpty) { - return const SizedBox.shrink(); - } + return Container( + height: responsiveUtils.isWebDesktop(context) ? 256 : null, + decoration: BoxDecoration( + border: Border.all(color: AppColor.attachmentFileBorderColor), + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + color: Colors.white + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + child: responsiveUtils.isWebDesktop(context) + ? _buildIdentityViewHorizontal(context) + : _buildIdentityViewVertical(context)) + ); + } - return Container( - foregroundDecoration: BoxDecoration( - border: Border.all(color: AppColor.colorBorderIdentityInfo, width: 1), - borderRadius: const BorderRadius.all(Radius.circular(16.0)), + Widget _buildIdentityViewVertical(BuildContext context) { + return Obx(() => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if(controller.isSignatureShow) + ...[ + _buildListIdentityView(context), + Container(height: 1, color: AppColor.attachmentFileBorderColor), + Obx(() => SignatureOfIdentityBuilder(identity: controller.identitySelected.value!)) + ] + else + _buildListIdentityView(context) + ], + )); + } + + Widget _buildIdentityViewHorizontal(BuildContext context) { + return Obx(() => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (controller.isSignatureShow) + ...[ + _buildListIdentityView(context), + Container(width: 1, color: AppColor.attachmentFileBorderColor), + Expanded( + child: Obx(() => SignatureOfIdentityBuilder(identity: controller.identitySelected.value!)), + ) + ] + else + Expanded(child: _buildListIdentityView(context)) + ], + )); + } + + Widget _buildListIdentityView(BuildContext context) { + return Container( + key: const Key('identities_list'), + width: responsiveUtils.isWebDesktop(context) ? 320 : null, + height: 256, + padding: const EdgeInsets.only(left: 12, top: 12, bottom: 12), + child: Obx(() => FadingEdgeScrollView.fromScrollView( + gradientFractionOnStart: 0.3, + gradientFractionOnEnd: 0.3, + shouldDisposeScrollController: true, + child: ListView.builder( + controller: ScrollController(), + padding: const EdgeInsets.only(right: 12.0), + itemCount: controller.listAllIdentities.length, + itemBuilder: ((context, index) { + return IdentityListTileBuilder( + imagePaths: imagePaths, + identity: controller.listAllIdentities[index], + identitySelected: controller.identitySelected.value, + onSelectIdentityAction: controller.selectIdentity, + onEditIdentityAction: (identitySelected) => + controller.goToEditIdentity(context, identitySelected), + onDeleteIdentityAction: (identitySelected) => + controller.openConfirmationDialogDeleteIdentityAction(context, identitySelected), + ); + }), ), - child: Obx(() => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - key: const Key('identities_checkbox_list'), - height: 256, - foregroundDecoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [0, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 1].map((e) - => Colors.white.withOpacity(e.toDouble())).toList(), - stops: const [0.8, 0.82, 0.84, 0.86, 0.88, 0.90, 0.92, 0.94, 0.96, 0.98, 1],// Gradient from https://learnui.design/tools/gradient-generator.html - tileMode: TileMode.mirror, - ), - border: controller.isSignatureShow() ? const Border(bottom: BorderSide(color: AppColor.colorBorderIdentityInfo)): null, - ), - padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, top: 12.0, right: 4.0), - child: Scrollbar( - child: MediaQuery.removePadding( - context: context, - removeTop: true, - child: ListView.builder( - padding: const EdgeInsets.only(right: 12.0), - itemCount: controller.listAllIdentities.value.length, - itemBuilder: ((context, index) { - return IdentityListTileBuilder( - controller: widget.controller, - index: index, - selected: widget.controller.selectedIndex.value, - ); - }), - ) - ), - ), - ), - if(controller.isSignatureShow()) - Obx(() => SignatureOfIdentityBuilder(identity: controller.identitySelected.value!)), - ], - ), - )); - }); + )) + ); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder_web.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder_web.dart deleted file mode 100644 index c19b5a038..000000000 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder_web.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get_state_manager/get_state_manager.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/signature_of_identity_builder.dart'; - -import 'identity_list_tile_builder.dart'; - -class IdentitiesRadioListBuilder extends StatefulWidget { - const IdentitiesRadioListBuilder({ - Key? key, - required this.controller, - }) : super(key: key); - - final IdentitiesController controller; - - @override - State createState() => _IdentitiesRadioListBuilderState(); -} - -class _IdentitiesRadioListBuilderState extends State { - int? selected; - late final allIdentities = widget.controller.listAllIdentities.value; - - @override - Widget build(BuildContext context) { - return Container( - height: 256, - foregroundDecoration: BoxDecoration( - border: Border.all(color: AppColor.colorBorderIdentityInfo, width: 1), - borderRadius: const BorderRadius.all(Radius.circular(16.0)), - ), - child: Obx(() => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if(widget.controller.isSignatureShow())...[ - _buildListViewContainer(), - Expanded( - child: Obx(() => SignatureOfIdentityBuilder(identity: widget.controller.identitySelected.value!)), - ) - ] else - Expanded(child: _buildListViewContainer()) - ], - )), - ); - } - - Widget _buildListViewContainer() { - return Container( - key: const Key('identities_checkbox_list'), - width: 320, - foregroundDecoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [0, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 1].map((e) - => Colors.white.withOpacity(e.toDouble())).toList(), - stops: const [0.8, 0.82, 0.84, 0.86, 0.88, 0.90, 0.92, 0.94, 0.96, 0.98, 1],// Gradient from https://learnui.design/tools/gradient-generator.html - tileMode: TileMode.mirror, - ), - border: widget.controller.isSignatureShow() ? const Border(right: BorderSide(color: AppColor.colorBorderIdentityInfo)): null, - ), - padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, top: 12.0, right: 4.0), - child: Scrollbar( - controller: ScrollController(), - radius: const Radius.circular(5.0), - thickness: 2.0, - child: Obx(() => ListView.builder( - padding: const EdgeInsets.only(right: 12.0), - itemCount: widget.controller.listAllIdentities.value.length, - itemBuilder: ((context, index) { - return IdentityListTileBuilder( - controller: widget.controller, - index: index, - selected: widget.controller.selectedIndex.value, - ); - }), - ), - )) - ); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart index 535091667..94412fea8 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart @@ -1,121 +1,167 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/identities_controller.dart'; +import 'package:model/extensions/list_email_address_extension.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnSelectIdentityAction = Function(Identity? identitySelected); +typedef OnEditIdentityAction = Function(Identity identitySelected); +typedef OnDeleteIdentityAction = Function(Identity identitySelected); class IdentityListTileBuilder extends StatelessWidget { + const IdentityListTileBuilder({ Key? key, - required this.controller, - required this.index, - required this.selected + required this.identity, + required this.identitySelected, + required this.imagePaths, + this.onSelectIdentityAction, + this.onEditIdentityAction, + this.onDeleteIdentityAction }) : super(key: key); - final IdentitiesController controller; - final int index; - final int? selected; + final Identity identity; + final Identity? identitySelected; + final ImagePaths imagePaths; + final OnSelectIdentityAction? onSelectIdentityAction; + final OnEditIdentityAction? onEditIdentityAction; + final OnDeleteIdentityAction? onDeleteIdentityAction; @override Widget build(BuildContext context) { - final imagePaths = controller.imagePaths; - final listAllIdentities = controller.listAllIdentities.value; - final identity = listAllIdentities[index]; - return Material( color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(12.0), - onTap: () => controller.selectIdentity(index), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - color: isIdentitySelected(index) ? AppColor.colorItemSelected : Colors.transparent, - ), - padding: const EdgeInsets.only(top: 12.0, bottom: 12.0, right: 12.0), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: BuildUtils.isWeb ? 8.0 : 0.0), - child: Radio( - value: index, - groupValue: selected, - onChanged: (selectedValue) => controller.selectIdentity(index), + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: InkWell( + borderRadius: BorderRadius.circular(12.0), + onTap: () => onSelectIdentityAction?.call(identity), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.0), + color: _isIdentitySelected + ? AppColor.colorItemSelected + : Colors.transparent, + ), + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Radio( + value: identity, + splashRadius: 15, + groupValue: identitySelected, + activeColor: AppColor.primaryColor, + onChanged: onSelectIdentityAction, ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - identity.name ?? '', - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black)), - _buildIconWithTextLine(imagePaths.icEmail, identity.email), - for(final widget in _buildReplyLines(identity, imagePaths)) - widget - ], - ) - ), - if(isIdentitySelected(index))...[ - buildSVGIconButton( - padding: const EdgeInsets.only(left: 12.0, right: 12.0), - icon: imagePaths.icEdit, - iconSize: 24, - iconColor: AppColor.primaryColor, - onTap: () => controller.goToEditIdentity(context, controller.identitySelected.value!), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Text( + identity.name ?? '', + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + ), + if (identity.email?.isNotEmpty == true) + _buildIconSVGWithTextLine(imagePaths.icEmail, identity.email), + if (identity.replyTo?.isNotEmpty == true) + _buildIconSVGWithTextLine( + imagePaths.icReplyTo, + identity.replyTo?.listEmailAddressToString(isFullEmailAddress: true) + ), + if (identity.bcc?.isNotEmpty == true) + _buildIconCharacterWithTextLine( + AppLocalizations.of(context).bcc_email_address_prefix, + identity.bcc?.listEmailAddressToString(isFullEmailAddress: true) + ), + ], + ) ), - buildSVGIconButton( - padding: const EdgeInsets.only(right: 0.0), - icon: imagePaths.icDeleteOutline, - iconSize: 24, - iconColor: AppColor.colorActionDeleteConfirmDialog, - onTap: () => controller.openConfirmationDialogDeleteIdentityAction(context, controller.identitySelected.value!), - ), - ] - ], + if(_isIdentitySelected) + ...[ + buildSVGIconButton( + icon: imagePaths.icEditRule, + iconSize: 24, + iconColor: AppColor.primaryColor, + onTap: () => onEditIdentityAction?.call(identity), + ), + buildSVGIconButton( + icon: imagePaths.icDeleteRule, + iconSize: 24, + iconColor: AppColor.colorDeletePermanentlyButton, + onTap: () => onDeleteIdentityAction?.call(identity), + ), + ] + ], + ), ), ), ), ); } - List _buildReplyLines(Identity? identity, ImagePaths imagePaths) { - return [ - for(var identityReplyTo in identity?.replyTo?.toList() ?? []) - _buildIconWithTextLine(imagePaths.icReply, identityReplyTo.email)]; - } - - Widget _buildIconWithTextLine(String imagePath, String? text) { - return Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: SvgPicture.asset( - imagePath, - color: AppColor.primaryColor, - width: 16, - fit: BoxFit.fitHeight, - )), - // replacement character after ..., so wrap it inside richtext is workaround solution + Widget _buildIconSVGWithTextLine(String imagePath, String? text) { + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row(children: [ + SizedBox( + width: 30, + child: Align( + alignment: Alignment.centerLeft, + child: SvgPicture.asset(imagePath, width: 15, height: 15))), + const SizedBox(width: 4), Expanded(child: Text( - text ?? '', style: const TextStyle( + text ?? '', + style: const TextStyle( color: AppColor.colorEmailAddressFull, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.normal, fontSize: 13, ), overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - )), - ] + softWrap: CommonTextStyle.defaultSoftWrap + )) + ]), ); } - bool isIdentitySelected(int index) { - return index == selected; + Widget _buildIconCharacterWithTextLine(String character, String? text) { + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row(children: [ + Container( + width: 30, + alignment: Alignment.centerLeft, + child: Text( + character, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + decoration: TextDecoration.underline, + color: AppColor.colorTextButton))), + const SizedBox(width: 4), + Expanded(child: Text(text ?? '', + style: const TextStyle( + color: AppColor.colorEmailAddressFull, + fontWeight: FontWeight.normal, + fontSize: 13, + ), + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap + )) + ]), + ); } + + bool get _isIdentitySelected => identity.id == identitySelected?.id; } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart index fa0b044a4..04bec652e 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart @@ -1,10 +1,14 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart'; +import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart'; +import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_web.dart'; +import 'package:core/utils/build_utils.dart'; import 'package:flutter/material.dart'; import 'package:html_unescape/html_unescape_small.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; class SignatureBuilder extends StatelessWidget { - SignatureBuilder({ + + const SignatureBuilder({ Key? key, this.width, this.height, @@ -61,5 +65,4 @@ class SignatureBuilder extends StatelessWidget { final unescape = HtmlUnescape(); return unescape.convert(htmlString); } - } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart index 06b3b477c..c7bc148a8 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart @@ -4,7 +4,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/profiles/iden class SignatureOfIdentityBuilder extends StatelessWidget { - SignatureOfIdentityBuilder({required this.identity}); + const SignatureOfIdentityBuilder({Key? key, required this.identity}) : super(key: key); final Identity identity; diff --git a/lib/features/manage_account/presentation/profiles/profiles_view.dart b/lib/features/manage_account/presentation/profiles/profiles_view.dart index f0f3a6c80..a81f8512d 100644 --- a/lib/features/manage_account/presentation/profiles/profiles_view.dart +++ b/lib/features/manage_account/presentation/profiles/profiles_view.dart @@ -1,15 +1,18 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/identities_view.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/profiles_controller.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/profiles/widgets/profiles_header_widget.dart'; class ProfilesView extends GetWidget { final _responsiveUtils = Get.find(); + final _imagePaths = Get.find(); ProfilesView({Key? key}) : super(key: key); @@ -17,52 +20,31 @@ class ProfilesView extends GetWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: SettingsUtils.getBackgroundColor(context, _responsiveUtils), - body: ResponsiveWidget( - responsiveUtils: _responsiveUtils, - mobile: IdentitiesView(), - desktop: _buildProfilesWeb(context)) - ); - } - - Widget _buildProfilesWeb(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(16.0)), - border: Border.all(color: AppColor.colorBorderIdentityInfo), - color: Colors.white, - ), - margin: const EdgeInsets.all(16.0), - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for(var widget in _buildProfilesTitle(context)) - widget, - const Divider(color: AppColor.lineItemListColor, height: 0.5, thickness: 0.2), - Expanded(child: IdentitiesView()), - ], - ), - ); - } - - List _buildProfilesTitle(BuildContext context) { - return [ - Text( - AppLocalizations.of(context).profiles.inCaps, - style: const TextStyle( - fontWeight: FontWeight.w700, - fontSize: 20, - fontFamily: 'Inter', - color: Colors.black)), - const SizedBox(height: 4.0), - Text( - AppLocalizations.of(context).profiles_description, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail), + body: Container( + width: double.infinity, + height: double.infinity, + color: SettingsUtils.getContentBackgroundColor(context, _responsiveUtils), + decoration: SettingsUtils.getBoxDecorationForContent(context, _responsiveUtils), + margin: SettingsUtils.getMarginViewForForwardSettingDetails(context, _responsiveUtils), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (_responsiveUtils.isWebDesktop(context)) + ...[ + ProfilesHeaderWidget(imagePaths: _imagePaths, responsiveUtils: _responsiveUtils), + Container(height: 1, color: AppColor.colorDividerHeaderSetting) + ], + Expanded(child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IdentitiesView() + ]) + )) + ], ), - const SizedBox(height: 16.0), - ]; + ), + ); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/profiles/widgets/profiles_header_widget.dart b/lib/features/manage_account/presentation/profiles/widgets/profiles_header_widget.dart new file mode 100644 index 000000000..097894dbd --- /dev/null +++ b/lib/features/manage_account/presentation/profiles/widgets/profiles_header_widget.dart @@ -0,0 +1,40 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class ProfilesHeaderWidget extends StatelessWidget { + + const ProfilesHeaderWidget({ + Key? key, + required this.imagePaths, + required this.responsiveUtils, + }) : super(key: key); + + final ImagePaths imagePaths; + final ResponsiveUtils responsiveUtils; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + padding: const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 15), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + AppLocalizations.of(context).profiles, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black)), + const SizedBox(height: 4), + Text( + AppLocalizations.of(context).profilesSettingExplanation, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorSettingExplanation)), + ]), + ); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 3ba13836c..c220ad6f6 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-11-18T14:33:10.063321", + "@@last_modified": "2022-12-28T14:09:51.971658", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -1166,12 +1166,30 @@ "placeholders_order": [], "placeholders": {} }, + "profilesSettingExplanation": "Info about you, and options to manage it", + "@profilesSettingExplanation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, "identities": "Identities", "@identities": { "type": "text", "placeholders_order": [], "placeholders": {} }, + "identitiesSettingExplanation": "Select the identity or email address you want to use to send an emails", + "@identitiesSettingExplanation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "createNewIdentity": "Create new identity", + "@createNewIdentity": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, "new_identity": "New Identity", "@new_identity": { "type": "text", @@ -2527,5 +2545,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "regards": "Regards", + "@regards": { + "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 796ac8cc2..235984764 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1200,9 +1200,11 @@ class AppLocalizations { name: 'profiles'); } - String get profiles_description { - return Intl.message('Info about you, and options to manage it', - name: 'profiles_description'); + String get profilesSettingExplanation { + return Intl.message( + 'Info about you, and options to manage it', + name: 'profilesSettingExplanation' + ); } String get identities { @@ -1210,14 +1212,16 @@ class AppLocalizations { name: 'identities'); } - String get identities_description { - return Intl.message('Select the identity or email address you want to use to send an emails', - name: 'identities_description'); + String get identitiesSettingExplanation { + return Intl.message( + 'Select the identity or email address you want to use to send an emails', + name: 'identitiesSettingExplanation'); } - String get create_new_identity { - return Intl.message('Create new identity', - name: 'create_new_identity'); + String get createNewIdentity { + return Intl.message( + 'Create new identity', + name: 'createNewIdentity'); } String get new_identity { diff --git a/pubspec.lock b/pubspec.lock index bb7d6de08..d8744a722 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -411,6 +411,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + fading_edge_scrollview: + dependency: "direct main" + description: + name: fading_edge_scrollview + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2aa563671..d6332964e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -207,7 +207,10 @@ dependencies: # Use for flutter 3.0.5 https://github.com/EdsonBueno/focus_detector/pull/14 focus_detector: git: https://github.com/ifnyas/focus_detector.git - html_unescape: ^2.0.0 + + html_unescape: 2.0.0 + + fading_edge_scrollview: 3.0.0 dev_dependencies: flutter_test: