diff --git a/assets/images/ic_align_center.svg b/assets/images/ic_align_center.svg new file mode 100644 index 000000000..4326d282e --- /dev/null +++ b/assets/images/ic_align_center.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/ic_align_indent.svg b/assets/images/ic_align_indent.svg new file mode 100644 index 000000000..9f2652ead --- /dev/null +++ b/assets/images/ic_align_indent.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_align_justify.svg b/assets/images/ic_align_justify.svg new file mode 100644 index 000000000..7c7ff2cef --- /dev/null +++ b/assets/images/ic_align_justify.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/ic_align_left.svg b/assets/images/ic_align_left.svg new file mode 100644 index 000000000..af9911ad0 --- /dev/null +++ b/assets/images/ic_align_left.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/ic_align_outdent.svg b/assets/images/ic_align_outdent.svg new file mode 100644 index 000000000..1d24c8033 --- /dev/null +++ b/assets/images/ic_align_outdent.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_align_right.svg b/assets/images/ic_align_right.svg new file mode 100644 index 000000000..9d07bed55 --- /dev/null +++ b/assets/images/ic_align_right.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 6bc09bbda..5e5884f74 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -137,6 +137,12 @@ class ImagePaths { String get icStyleHeader => _getImagePath('ic_style_header.svg'); String get icStyleColor => _getImagePath('ic_style_color.svg'); String get icStyleArrowDown => _getImagePath('ic_style_arrow_down.svg'); + String get icAlignLeft => _getImagePath('ic_align_left.svg'); + String get icAlignCenter => _getImagePath('ic_align_center.svg'); + String get icAlignRight => _getImagePath('ic_align_right.svg'); + String get icAlignJustify => _getImagePath('ic_align_justify.svg'); + String get icAlignIndent => _getImagePath('ic_align_indent.svg'); + String get icAlignOutdent => _getImagePath('ic_align_outdent.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/features/base/widget/drop_down_button_widget.dart b/lib/features/base/widget/drop_down_button_widget.dart index cdf064939..d65f16dad 100644 --- a/lib/features/base/widget/drop_down_button_widget.dart +++ b/lib/features/base/widget/drop_down_button_widget.dart @@ -26,6 +26,7 @@ class DropDownButtonWidget extends StatelessWidget { final double opacity; final Widget? iconArrowDown; final Color? colorButton; + final String tooltip; const DropDownButtonWidget({ Key? key, @@ -41,6 +42,7 @@ class DropDownButtonWidget extends StatelessWidget { this.opacity = 1.0, this.iconArrowDown, this.colorButton = Colors.white, + this.tooltip = '', }) : super(key: key); @override @@ -91,28 +93,31 @@ class DropDownButtonWidget extends StatelessWidget { .toList(), value: itemSelected, customButton: supportSelectionIcon - ? Container( - height: heightItem, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(radiusButton), - border: Border.all( - color: AppColor.colorInputBorderCreateMailbox, - width: 0.5, + ? Tooltip( + message: tooltip, + child: Container( + height: heightItem, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radiusButton), + border: Border.all( + color: AppColor.colorInputBorderCreateMailbox, + width: 0.5, + ), + color: colorButton ?? AppColor.colorInputBackgroundCreateMailbox, ), - color: colorButton ?? AppColor.colorInputBackgroundCreateMailbox, + padding: const EdgeInsets.only(left: 12, right: 10), + child: Row(children: [ + Expanded(child: Text( + _getTextItemDropdown(context, item: itemSelected), + style: TextStyle(fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black.withOpacity(opacity)), + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + iconArrowDown ?? SvgPicture.asset(_imagePaths.icDropDown) + ]), ), - padding: const EdgeInsets.only(left: 12, right: 10), - child: Row(children: [ - Expanded(child: Text( - _getTextItemDropdown(context, item: itemSelected), - style: TextStyle(fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black.withOpacity(opacity)), - maxLines: 1, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - iconArrowDown ?? SvgPicture.asset(_imagePaths.icDropDown) - ]), ) : null, onChanged: onChanged, diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 7256c4759..8b6dcf91c 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -10,6 +10,7 @@ import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:model/model.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/base/widget/popup_menu_overlay_widget.dart'; import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart'; import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; @@ -17,6 +18,7 @@ import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_but import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu_font_status.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/font_name_type.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/paragraph_type.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_file_composer_builder.dart'; @@ -624,12 +626,7 @@ class ComposerView extends GetWidget Future.delayed(const Duration(milliseconds: 500), () { controller.richTextWebController.editorController.setFocus(); }); - if (controller.richTextWebController.isMenuFontOpen) { - controller.richTextWebController.closeDropdownMenuFont(); - } - if (controller.richTextWebController.isMenuHeaderStyleOpen) { - controller.richTextWebController.closeDropdownMenuHeaderStyle(); - } + controller.richTextWebController.closeAllMenuPopup(); }, onBlur: () { controller.onEditorFocusChange(false); }, onMouseDown: () { @@ -766,19 +763,19 @@ class ComposerView extends GetWidget padding: const EdgeInsets.only(left: 20, top: 8, bottom: 8), alignment: Alignment.centerLeft, child: Wrap( - alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: 8, children: [ AbsorbPointer( absorbing: codeViewEnabled, child: DropDownMenuHeaderStyleWidget( icon: buildWrapIconStyleText( - icon: Tooltip( - message: RichTextStyleType.headerStyle.getTooltipButton(context), - child: SvgPicture.asset(RichTextStyleType.headerStyle.getIcon(imagePaths), - color: AppColor.colorDefaultRichTextButton.withOpacity(opacity), - fit: BoxFit.fill), - ) + isSelected: richTextController.isMenuHeaderStyleOpen, + icon: SvgPicture.asset(RichTextStyleType.headerStyle.getIcon(imagePaths), + color: AppColor.colorDefaultRichTextButton.withOpacity(opacity), + fit: BoxFit.fill), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + tooltip: RichTextStyleType.headerStyle.getTooltipButton(context) ), items: HeaderStyleType.values, onMenuStateChange: (isOpen) { @@ -786,7 +783,7 @@ class ComposerView extends GetWidget final newStatus = isOpen ? DropdownMenuFontStatus.open : DropdownMenuFontStatus.closed; - richTextController.menuHeaderStyleStatus = newStatus; + richTextController.menuHeaderStyleStatus.value = newStatus; }, onChanged: (newStyle) => richTextController.applyHeaderStyle(newStyle)), ), @@ -804,32 +801,39 @@ class ComposerView extends GetWidget final newStatus = isOpen ? DropdownMenuFontStatus.open : DropdownMenuFontStatus.closed; - richTextController.menuFontStatus = newStatus; + richTextController.menuFontStatus.value = newStatus; }, - heightItem: 38, + heightItem: 40, sizeIconChecked: 16, - radiusButton: 5, + radiusButton: 8, opacity: opacity, - colorButton: Colors.white, + colorButton: richTextController.isMenuFontOpen + ? AppColor.colorBackgroundWrapIconStyleCode + : Colors.white, iconArrowDown: SvgPicture.asset(imagePaths.icStyleArrowDown), + tooltip: RichTextStyleType.fontName.getTooltipButton(context), supportSelectionIcon: true)), ), - AbsorbPointer( - absorbing: codeViewEnabled, - child: buildWrapIconStyleText( - icon: buildIconWithTooltip( - path: RichTextStyleType.textColor.getIcon(imagePaths), - color: richTextController.selectedTextColor.value, - tooltip: RichTextStyleType.textColor.getTooltipButton(context), - opacity: opacity), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.textColor)), - ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.only(right: 8), child: AbsorbPointer( absorbing: codeViewEnabled, child: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5), + icon: buildIconWithTooltip( + path: RichTextStyleType.textColor.getIcon(imagePaths), + color: richTextController.selectedTextColor.value, + tooltip: RichTextStyleType.textColor.getTooltipButton(context), + opacity: opacity), + onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.textColor)), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 8), + child: AbsorbPointer( + absorbing: codeViewEnabled, + child: buildWrapIconStyleText( + padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 7), + spacing: 3, icon: buildIconColorBackgroundText( iconData: RichTextStyleType.textBackgroundColor.getIconData(), colorSelected: richTextController.selectedTextBackgroundColor.value, @@ -838,52 +842,79 @@ class ComposerView extends GetWidget onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.textBackgroundColor)), ), ), - buildWrapIconStyleText( - hasDropdown: false, - padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5), - icon: Wrap(children: [ - AbsorbPointer( - absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.bold.getIcon(imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.bold), - tooltip: RichTextStyleType.bold.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.bold)), - ), - AbsorbPointer( - absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.italic.getIcon(imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.italic), - tooltip: RichTextStyleType.italic.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.italic)), - ), - AbsorbPointer( - absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.underline.getIcon(imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.underline), - tooltip: RichTextStyleType.underline.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.underline)), - ), - AbsorbPointer( - absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.strikeThrough.getIcon(imagePaths), - isSelected: richTextController.isTextStyleTypeSelected( - RichTextStyleType.strikeThrough), - tooltip: RichTextStyleType.strikeThrough.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.strikeThrough)), - ) - ])), Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.only(right: 8.0), child: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 5), + hasDropdown: false, + padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5), + icon: Wrap(children: [ + AbsorbPointer( + absorbing: codeViewEnabled, + child: buildIconStyleText( + path: RichTextStyleType.bold.getIcon(imagePaths), + isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.bold), + tooltip: RichTextStyleType.bold.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.bold)), + ), + AbsorbPointer( + absorbing: codeViewEnabled, + child: buildIconStyleText( + path: RichTextStyleType.italic.getIcon(imagePaths), + isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.italic), + tooltip: RichTextStyleType.italic.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.italic)), + ), + AbsorbPointer( + absorbing: codeViewEnabled, + child: buildIconStyleText( + path: RichTextStyleType.underline.getIcon(imagePaths), + isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.underline), + tooltip: RichTextStyleType.underline.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.underline)), + ), + AbsorbPointer( + absorbing: codeViewEnabled, + child: buildIconStyleText( + path: RichTextStyleType.strikeThrough.getIcon(imagePaths), + isSelected: richTextController.isTextStyleTypeSelected( + RichTextStyleType.strikeThrough), + tooltip: RichTextStyleType.strikeThrough.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.strikeThrough)), + ) + ])), + ), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: AbsorbPointer( + absorbing: codeViewEnabled, + child: PopupMenuOverlayWidget( + controller: richTextController.menuParagraphController, + listButtonAction: ParagraphType.values + .map((paragraph) => paragraph.buildButtonWidget( + context, + imagePaths, + (paragraph) => richTextController.applyParagraphType(paragraph))) + .toList(), + iconButton: buildWrapIconStyleText( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + spacing: 3, + opacity: opacity, + isSelected: richTextController.focusMenuParagraph.value, + icon: buildIconWithTooltip( + path: richTextController.selectedParagraph.value.getIcon(imagePaths), + color: AppColor.colorDefaultRichTextButton.withOpacity(opacity), + tooltip: RichTextStyleType.paragraph.getTooltipButton(context))), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 8), + child: buildWrapIconStyleText( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.codeView), icon: buildIconWithTooltip( path: RichTextStyleType.codeView.getIcon(imagePaths), diff --git a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart index d98a641ac..139e8323d 100644 --- a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart +++ b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart @@ -15,29 +15,41 @@ mixin RichTextButtonMixin { bool hasDropdown = true, double opacity = 1.0, EdgeInsets? padding, + double? spacing, + String tooltip = '', }){ - return InkWell( - onTap: onTap, + final buttonIcon = Tooltip( + message: tooltip, child: Container( padding: padding ?? const EdgeInsets.symmetric(horizontal: 5, vertical: 5), decoration: BoxDecoration( color: isSelected == true - ? AppColor.colorBackgroundWrapIconStyleCode.withOpacity(opacity) - : Colors.white.withOpacity(opacity), + ? AppColor.colorBackgroundWrapIconStyleCode + : Colors.white, border: Border.all( color: AppColor.colorBorderWrapIconStyleCode, width: 0.5), borderRadius: BorderRadius.circular(8)), child: hasDropdown - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - icon, - SvgPicture.asset(_imagePaths.icStyleArrowDown) - ]) - : icon, + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + icon, + if (spacing != null) SizedBox(width: spacing), + SvgPicture.asset(_imagePaths.icStyleArrowDown) + ]) + : icon, ), ); + + if (onTap != null) { + return InkWell( + onTap: onTap, + child: buttonIcon, + ); + } else { + return buttonIcon; + } } Widget buildIconStyleText({ diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 6fe2fa1d2..e2d6bea9b 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-07-22T18:31:58.383010", + "@@last_modified": "2022-07-26T16:21:05.066657", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -1681,5 +1681,53 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "fontFamily": "Font Family", + "@fontFamily": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "paragraph": "Paragraph", + "@paragraph": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "alignLeft": "Align left", + "@alignLeft": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "alignRight": "Align right", + "@alignRight": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "alignCenter": "Align center", + "@alignCenter": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "justifyFull": "Justify full", + "@justifyFull": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "outdent": "Outdent", + "@outdent": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "indent": "Indent", + "@indent": { + "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 4043d70c3..624527032 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1725,4 +1725,52 @@ class AppLocalizations { 'Style', name: 'headerStyle'); } + + String get fontFamily { + return Intl.message( + 'Font Family', + name: 'fontFamily'); + } + + String get paragraph { + return Intl.message( + 'Paragraph', + name: 'paragraph'); + } + + String get alignLeft { + return Intl.message( + 'Align left', + name: 'alignLeft'); + } + + String get alignRight { + return Intl.message( + 'Align right', + name: 'alignRight'); + } + + String get alignCenter { + return Intl.message( + 'Align center', + name: 'alignCenter'); + } + + String get justifyFull { + return Intl.message( + 'Justify full', + name: 'justifyFull'); + } + + String get outdent { + return Intl.message( + 'Outdent', + name: 'outdent'); + } + + String get indent { + return Intl.message( + 'Indent', + name: 'indent'); + } } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 6d8ca45b4..92fbbc86d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -174,6 +174,8 @@ dependencies: # HTML5 parser html: 0.15.0 + custom_pop_up_menu: 1.2.4 + dev_dependencies: flutter_test: sdk: flutter