From 200e106c1999c29f00a1728cd55b544860c9c14c Mon Sep 17 00:00:00 2001 From: sherlockvn Date: Tue, 27 Dec 2022 14:17:37 +0700 Subject: [PATCH] TF-1194 apply new design identity to manage identity screen in mobile --- assets/images/ic_delete_outline.svg | 10 + .../extensions/color_extension.dart | 2 + .../presentation/resources/image_paths.dart | 1 + core/lib/presentation/utils/theme_utils.dart | 4 + .../html_content_viewer_widget.dart | 8 + .../profiles/identities/identities_view.dart | 271 ++++++------------ .../identities_radio_list_builder.dart | 79 +++++ .../widgets/identity_list_tile_builder.dart | 121 ++++++++ .../identities/widgets/signature_builder.dart | 65 +++++ .../signature_of_identity_builder.dart | 20 ++ lib/main/localizations/app_localizations.dart | 22 ++ pubspec.lock | 7 + pubspec.yaml | 1 + 13 files changed, 435 insertions(+), 176 deletions(-) create mode 100644 assets/images/ic_delete_outline.svg create mode 100644 lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart create mode 100644 lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart create mode 100644 lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart create mode 100644 lib/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart diff --git a/assets/images/ic_delete_outline.svg b/assets/images/ic_delete_outline.svg new file mode 100644 index 000000000..214e3a8d8 --- /dev/null +++ b/assets/images/ic_delete_outline.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 3756610af..d22cddd97 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -73,6 +73,7 @@ extension AppColor on Color { static const colorTextButton = Color(0xFF007AFF); static const colorHintSearchBar = Color(0xFF818C99); static const colorBgSearchBar = Color(0x99EBEDF0); + static const colorBgIdentityButton = Color(0x00EBEDF0); static const colorShadowBgContentEmail = Color(0x14000000); static const colorDividerMailbox = Color(0xFF99A2AD); static const colorCollapseMailbox = Color(0xFFB8C1CC); @@ -167,6 +168,7 @@ extension AppColor on Color { static const colorTitleQuotasWarning = Color(0xFFF05C44); static const colorProgressQuotasWarning = Color(0xFFFFA000); static const colorOutOfStorageQuotasWarning = Color(0xffE64646); + static const colorThumbScrollBar = Color(0xFFAEB7C2); 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 de00cbe38..7d9e3d0ee 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -26,6 +26,7 @@ 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/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index 44ab20474..843d1d14a 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -12,6 +12,10 @@ class ThemeUtils { appBarTheme: _appBarTheme, textTheme: _textTheme, visualDensity: VisualDensity.adaptivePlatformDensity, + scrollbarTheme: ScrollbarThemeData( + thickness: MaterialStateProperty.all(2.0), + radius: const Radius.circular(5.0), + thumbColor: MaterialStateProperty.all(AppColor.colorThumbScrollBar)), ); } diff --git a/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart b/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart index 24bfa928e..c420143d6 100644 --- a/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart +++ b/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart @@ -68,6 +68,14 @@ class _HtmlContentViewState extends State { _htmlData = generateHtml(widget.contentHtml); } + @override + void didUpdateWidget(covariant HtmlContentViewer oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.contentHtml != oldWidget.contentHtml) { + _htmlData = generateHtml(widget.contentHtml); + } + } + @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { 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 124abfaea..153c9ab50 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_view.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_view.dart @@ -1,22 +1,12 @@ - -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/state/success.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; -import 'package:core/presentation/views/button/button_builder.dart'; -import 'package:core/presentation/views/responsive/responsive_widget.dart'; +import 'package:core/core.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/base/widget/drop_down_button_widget.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/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 { @@ -27,177 +17,106 @@ class IdentitiesView extends GetWidget with PopupMenuWidge @override Widget build(BuildContext context) { - final buttonSelectIdentity = Row(children: [ - Expanded(child: Obx(() => DropDownButtonWidget( - items: controller.listAllIdentities, - itemSelected: controller.identitySelected.value, - onChanged: (newIdentity) => controller.selectIdentity(newIdentity), - supportSelectionIcon: true))), - if (!_responsiveUtils.isMobile(context)) const SizedBox(width: 12), - if (!_responsiveUtils.isMobile(context)) - (ButtonBuilder(_imagePaths.icAddIdentity) - ..key(const Key('button_new_identity')) - ..decoration(BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: AppColor.colorTextButton)) - ..paddingIcon(const EdgeInsets.only(right: 8)) - ..iconColor(Colors.white) - ..maxWidth(170) - ..size(20) - ..radiusSplash(10) - ..padding(const EdgeInsets.symmetric(vertical: 12)) - ..textStyle(const TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w500)) - ..onPressActionClick(() => controller.goToCreateNewIdentity(context)) - ..text(AppLocalizations.of(context).new_identity, isVertical: false)) - .build() - ]); + return ResponsiveWidget( + responsiveUtils: _responsiveUtils, + mobile: _buildIdentitiesViewMobile(context), + tablet: _buildIdentitiesViewMobile(context), + desktop: _buildIdentitiesViewWeb(context), + tabletLarge: _buildIdentitiesViewMobile(context), + ); + } - return LayoutBuilder(builder: (context, constraints) => ResponsiveWidget( - responsiveUtils: _responsiveUtils, - mobile: Scaffold( - body: Container( - margin: const EdgeInsets.symmetric(vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buttonSelectIdentity, - 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( - key: const Key('add_new_identity'), - onPressed: () => controller.goToCreateNewIdentity(context), - backgroundColor: AppColor.primaryColor, - child: SvgPicture.asset(_imagePaths.icAddIdentity, width: 24, height: 24)) - : null), - desktop: Container( - margin: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), + 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: [ - SizedBox( - width: constraints.maxWidth / 2, - child: buttonSelectIdentity), - 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)) - ))) - ] - ) - ) - )); + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for(Widget widget in _buildIdentitiesTitles(context)) + widget, + _buildCreateIdentityButton(context), + ], + ), + ), + Expanded( + child: identities_listview_web.IdentitiesRadioListBuilder(controller: controller), + ), + ], + ), + ); } - 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)) { - controller.openContextMenuAction(context, _bottomSheetIdentityActionTiles(context, identity)); - } else { - controller.openPopupMenuAction(context, position, _popupMenuIdentityActionTiles(context, identity)); - } - } - - List _popupMenuIdentityActionTiles(BuildContext context, Identity identity) { + List _buildIdentitiesTitles(BuildContext context) { return [ - PopupMenuItem( - padding: EdgeInsets.zero, - child: popupItem(_imagePaths.icEdit, - AppLocalizations.of(context).edit_identity, - styleName: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 17, - color: Colors.black), - onCallbackAction: () => controller.goToEditIdentity(context, identity))), - if (identity.mayDelete == true) - PopupMenuItem( - padding: EdgeInsets.zero, - child: popupItem(_imagePaths.icDeleteComposer, - AppLocalizations.of(context).delete_identity, - colorIcon: AppColor.colorActionDeleteConfirmDialog, - styleName: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 17, - color: AppColor.colorActionDeleteConfirmDialog), - onCallbackAction: () => controller.openConfirmationDialogDeleteIdentityAction(context, identity))), + 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), ]; } - List _bottomSheetIdentityActionTiles(BuildContext context, Identity identity) { - return [ - _editIdentityActionTile(context, identity), - if (identity.mayDelete == true) - _deleteIdentityActionTile(context, identity), - ]; + Widget _buildCreateIdentityButton(BuildContext context) { + return Row( + 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(), + ), + ), + ], + ); } - Widget _deleteIdentityActionTile(BuildContext context, Identity identity) { - return (IdentityBottomSheetActionTileBuilder( - const Key('delete_identity_action'), - SvgPicture.asset(_imagePaths.icDeleteComposer, - color: AppColor.colorActionDeleteConfirmDialog), - AppLocalizations.of(context).delete_identity, - identity, - iconLeftPadding: _responsiveUtils.isMobile(context) - ? const EdgeInsets.only(left: 12, right: 16) - : const EdgeInsets.only(right: 12), - iconRightPadding: _responsiveUtils.isMobile(context) - ? const EdgeInsets.only(right: 12) - : EdgeInsets.zero) - ..onActionClick((identity) => controller.openConfirmationDialogDeleteIdentityAction(context, identity))) - .build(); - } - - Widget _editIdentityActionTile(BuildContext context, Identity identity) { - return (IdentityBottomSheetActionTileBuilder( - const Key('edit_identity_action'), - SvgPicture.asset(_imagePaths.icEdit), - AppLocalizations.of(context).edit_identity, - identity, - iconLeftPadding: _responsiveUtils.isMobile(context) - ? const EdgeInsets.only(left: 12, right: 16) - : const EdgeInsets.only(right: 12), - iconRightPadding: _responsiveUtils.isMobile(context) - ? const EdgeInsets.only(right: 12) - : EdgeInsets.zero) - ..onActionClick((identity) => controller.goToEditIdentity(context, identity))) - .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 new file mode 100644 index 000000000..5b19dab4d --- /dev/null +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/identities_radio_list_builder.dart @@ -0,0 +1,79 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.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 { + late final controller = widget.controller; + + @override + Widget build(BuildContext context) { + return Obx(() { + if (controller.listAllIdentities.isEmpty) { + return const SizedBox.shrink(); + } + + return Container( + foregroundDecoration: BoxDecoration( + border: Border.all(color: AppColor.colorBorderIdentityInfo, width: 1), + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + ), + 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/identity_list_tile_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart new file mode 100644 index 000000000..535091667 --- /dev/null +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/identity_list_tile_builder.dart @@ -0,0 +1,121 @@ +import 'package:core/core.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'; + +class IdentityListTileBuilder extends StatelessWidget { + const IdentityListTileBuilder({ + Key? key, + required this.controller, + required this.index, + required this.selected + }) : super(key: key); + + final IdentitiesController controller; + final int index; + final int? selected; + + @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), + ), + ), + 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!), + ), + buildSVGIconButton( + padding: const EdgeInsets.only(right: 0.0), + icon: imagePaths.icDeleteOutline, + iconSize: 24, + iconColor: AppColor.colorActionDeleteConfirmDialog, + onTap: () => controller.openConfirmationDialogDeleteIdentityAction(context, controller.identitySelected.value!), + ), + ] + ], + ), + ), + ), + ); + } + + 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 + Expanded(child: Text( + text ?? '', style: const TextStyle( + color: AppColor.colorEmailAddressFull, + fontWeight: FontWeight.w400, + fontSize: 13, + ), + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + )), + ] + ); + } + + bool isIdentitySelected(int index) { + return index == selected; + } +} \ 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 new file mode 100644 index 000000000..fa0b044a4 --- /dev/null +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart @@ -0,0 +1,65 @@ +import 'package:core/core.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({ + Key? key, + this.width, + this.height, + this.htmlSignature, + this.textSignature, + }) : super(key: key); + + final Signature? htmlSignature; + final Signature? textSignature; + final double? width; + final double? height; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final signatureWidth = width ?? constraints.biggest.width; + final signatureHeight = height ?? constraints.biggest.height; + return Container( + width: signatureWidth, + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + decoration: const BoxDecoration( + color: Colors.white, + ), + child: _buildSignature(signatureWidth, signatureHeight), + ); + } + ); + } + + Widget _buildSignature(double width, double height) { + Widget signature; + if (htmlSignature != null && htmlSignature!.value.isNotEmpty) { + final htmlSignatureDecoded = _decodeHtml(htmlSignature!.value); + if (BuildUtils.isWeb) { + signature = HtmlContentViewerOnWeb( + contentHtml: htmlSignatureDecoded, + widthContent: width, + heightContent: height, + controller: HtmlViewerControllerForWeb(), + allowResizeToDocumentSize: false); + } else { + signature = HtmlContentViewer(contentHtml: htmlSignatureDecoded, heightContent: height); + } + } else if (textSignature != null) { + signature = Text(textSignature!.value); + } else { + signature = SizedBox.fromSize(size: Size(width, height)); + } + return signature; + } + + String _decodeHtml(String htmlString) { + 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 new file mode 100644 index 000000000..06b3b477c --- /dev/null +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_of_identity_builder.dart @@ -0,0 +1,20 @@ +import 'package:flutter/cupertino.dart'; +import 'package:jmap_dart_client/jmap/identities/identity.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart'; + +class SignatureOfIdentityBuilder extends StatelessWidget { + + SignatureOfIdentityBuilder({required this.identity}); + + final Identity identity; + + @override + Widget build(BuildContext context) { + return SignatureBuilder( + height: 256, + htmlSignature: identity.htmlSignature, + textSignature: identity.textSignature + ); + } +} + diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index da402af62..796ac8cc2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1200,11 +1200,26 @@ class AppLocalizations { name: 'profiles'); } + String get profiles_description { + return Intl.message('Info about you, and options to manage it', + name: 'profiles_description'); + } + String get identities { return Intl.message('Identities', 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 create_new_identity { + return Intl.message('Create new identity', + name: 'create_new_identity'); + } + String get new_identity { return Intl.message('New Identity', name: 'new_identity'); @@ -2594,4 +2609,11 @@ class AppLocalizations { name: 'openInNewTab', ); } + + String get regards { + return Intl.message( + 'Regards', + name: 'regards', + ); + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index b7ca04234..bb7d6de08 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -789,6 +789,13 @@ packages: url: "https://github.com/linagora/html-editor-enhanced.git" source: git version: "2.5.0" + html_unescape: + dependency: "direct main" + description: + name: html_unescape + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 63b060ade..2aa563671 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -207,6 +207,7 @@ 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 dev_dependencies: flutter_test: