diff --git a/core/lib/core.dart b/core/lib/core.dart index f887e6f075..3d5c4046fb 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -86,7 +86,6 @@ export 'presentation/views/quick_search/quick_search_input_form.dart'; export 'presentation/views/toast/toast_position.dart'; export 'presentation/views/toast/tmail_toast.dart'; export 'presentation/views/bottom_popup/full_screen_action_sheet_builder.dart'; -export 'presentation/views/checkbox/labeled_checkbox.dart'; export 'presentation/views/container/tmail_container_widget.dart'; export 'presentation/views/clipper/side_arrow_clipper.dart'; export 'presentation/views/avatar/gradient_circle_avatar_icon.dart'; diff --git a/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart b/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart index 2698acb264..2e0a47adec 100644 --- a/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart +++ b/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart @@ -54,6 +54,7 @@ class ConfirmationDialogActionSheetBuilder { return await showCupertinoModalPopup( context: _context, barrierColor: AppColor.colorDefaultCupertinoActionSheet, + barrierDismissible: false, builder: (context) => PointerInterceptor(child: CupertinoActionSheet( actions: [ if (_messageText != null && _messageText!.isNotEmpty) diff --git a/core/lib/presentation/views/button/tmail_button_widget.dart b/core/lib/presentation/views/button/tmail_button_widget.dart index b6d06720fb..7a80e6b72b 100644 --- a/core/lib/presentation/views/button/tmail_button_widget.dart +++ b/core/lib/presentation/views/button/tmail_button_widget.dart @@ -40,6 +40,7 @@ class TMailButtonWidget extends StatelessWidget { final MainAxisSize mainAxisSize; final bool isLoading; final Color? hoverColor; + final bool expandedText; const TMailButtonWidget({ super.key, @@ -74,6 +75,7 @@ class TMailButtonWidget extends StatelessWidget { this.mainAxisSize = MainAxisSize.max, this.isLoading = false, this.hoverColor, + this.expandedText = false, }); factory TMailButtonWidget.fromIcon({ @@ -149,6 +151,7 @@ class TMailButtonWidget extends StatelessWidget { BoxBorder? border, int? maxLines, Color? hoverColor, + bool expandedText = false, }) { return TMailButtonWidget( key: key, @@ -172,6 +175,7 @@ class TMailButtonWidget extends StatelessWidget { border: border, maxLines: maxLines, hoverColor: hoverColor, + expandedText: expandedText, ); } @@ -244,6 +248,20 @@ class TMailButtonWidget extends StatelessWidget { softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, ), ) + else if (expandedText) + Expanded( + child: Text( + text, + textAlign: textAlign, + style: textStyle ?? const TextStyle( + fontSize: 12, + color: AppColor.colorTextButtonHeaderThread + ), + maxLines: maxLines, + overflow: maxLines == 1 ? CommonTextStyle.defaultTextOverFlow : null, + softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, + ), + ) else Text( text, @@ -288,6 +306,20 @@ class TMailButtonWidget extends StatelessWidget { softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, ), ) + else if (expandedText) + Expanded( + child: Text( + text, + textAlign: textAlign, + style: textStyle ?? const TextStyle( + fontSize: 12, + color: AppColor.colorTextButtonHeaderThread + ), + maxLines: maxLines, + overflow: maxLines == 1 ? CommonTextStyle.defaultTextOverFlow : null, + softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, + ), + ) else Text( text, diff --git a/core/lib/presentation/views/checkbox/labeled_checkbox.dart b/core/lib/presentation/views/checkbox/labeled_checkbox.dart deleted file mode 100644 index b2a511491e..0000000000 --- a/core/lib/presentation/views/checkbox/labeled_checkbox.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; - -class LabeledCheckbox extends StatelessWidget { - const LabeledCheckbox({Key? key, - required this.label, - this.contentPadding, - this.value, - this.onChanged, - this.activeColor, - this.fontSize = 16, - this.gap = 4.0, - this.bold = false, - this.focusNode, - }) : super(key: key); - - final String label; - final EdgeInsets? contentPadding; - final bool? value; - final Function(bool?)? onChanged; - final Color? activeColor; - final double fontSize; - final double gap; - final bool bold; - final FocusNode? focusNode; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => onChanged?.call(!(value ?? false)), - child: Padding( - padding: contentPadding ?? const EdgeInsets.all(0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - value: value, - activeColor: activeColor, - visualDensity: VisualDensity.compact, - focusNode: focusNode, - onChanged: onChanged, - ), - SizedBox( - width: gap, - ), - Flexible( - child: Text( - label, - style: TextStyle( - fontSize: fontSize, - fontWeight: bold ? FontWeight.bold : FontWeight.normal, - color: Colors.black - ), - ), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/core/lib/presentation/views/container/tmail_container_widget.dart b/core/lib/presentation/views/container/tmail_container_widget.dart index a23bc11361..a71c830970 100644 --- a/core/lib/presentation/views/container/tmail_container_widget.dart +++ b/core/lib/presentation/views/container/tmail_container_widget.dart @@ -49,6 +49,7 @@ class TMailContainerWidget extends StatelessWidget { type: MaterialType.transparency, child: InkWell( onTap: onTapActionCallback, + excludeFromSemantics: true, onTapDown: onTapActionAtPositionCallback != null ? (detail) { if (onTapActionAtPositionCallback != null) { diff --git a/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart b/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart index 4bd8b2e477..a66098b2de 100644 --- a/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart @@ -150,7 +150,8 @@ class ColorPickerDialogBuilder { }) ], ), - ) + ), + barrierDismissible: false ); } diff --git a/docs/adr/0052-enable-semantics-to-support-integration-test-on-web.md b/docs/adr/0052-enable-semantics-to-support-integration-test-on-web.md new file mode 100644 index 0000000000..427f0d21a5 --- /dev/null +++ b/docs/adr/0052-enable-semantics-to-support-integration-test-on-web.md @@ -0,0 +1,84 @@ +# 52. Enable Semantics to support integration test on Web + +Date: 2024-08-19 + +## Status + +Accepted + +## Context + +All the UI in a Flutter app is rendered as a `Widget Tree`. So the widgets can't access the widget tree elements. +So we have to use [Semantics](https://api.flutter.dev/flutter/widgets/Semantics-class.html) +so that when Flutter renders the `Widgets Tree`, it also maintains a second tree, called `Semantics Tree`, +which helps us to easily access each element, widget of the user interface. + +## Decision + +1. How to enable Semantics: + +- Enable `Semantics` in main function: + +```dart +void main() { + WidgetsFlutterBinding.ensureInitialized(); + SemanticsBinding.instance.ensureSemantics(); + + runApp(const TMailApp()); +} +``` + +- To access any widget element we just need to wrap it in a `Sematics` widget and set a `label` for it + +```dart +Semantics( + label: 'Title-app', + child: Text('Twake Mail'), +); +``` + +2. To ignore `Sematics` for a specific widget, when we have Semantics enabled + +- Way 1: Set the `excludeSemantics: true` property of the `Semantics` widget + +```dart +Semantics( + label: 'Title-app', + excludeSemantics: true, + child: Text('Twake Mail'), +); +``` + +- Way 2: Use `ExcludeSemantics` widget instead of `Semantics` widget + +```dart +ExcludeSemantics( + child: Text('Twake Mail'), +); +``` + +3. For a widget to be treated as a text field in the Semantics Tree + +Set the ` textField: true` property of the `Semantics` widget + +```dart +Semantics( + label: 'Subject-email', + textField: true, + child: TextField(), +); +``` + +## Consequences + +- Enabling Semantics has given us easy access to the widget elements of each screen. Making `Integration test` implementation easy and efficient. + +## Influence + +Some widgets are not working due to Semantics overlap: + +- Workaround: + - Ignore `Sematics` for a specific widget + - Use the correct properties for each widget used (`textField, button, container,...`) + + diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart index 854eca61a7..458e33e872 100644 --- a/lib/features/base/base_mailbox_controller.dart +++ b/lib/features/base/base_mailbox_controller.dart @@ -369,6 +369,7 @@ abstract class BaseMailboxController extends BaseController { (value) => onRenameMailboxAction(presentationMailbox, MailboxName(value)) ) ).build()), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } @@ -437,6 +438,7 @@ abstract class BaseMailboxController extends BaseController { ..onConfirmButtonAction(AppLocalizations.of(context).delete, () => onDeleteMailboxAction(presentationMailbox)) ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack()) ).build()), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/base/mixin/mailbox_action_handler_mixin.dart b/lib/features/base/mixin/mailbox_action_handler_mixin.dart index f124709534..8557ae886f 100644 --- a/lib/features/base/mixin/mailbox_action_handler_mixin.dart +++ b/lib/features/base/mixin/mailbox_action_handler_mixin.dart @@ -100,6 +100,7 @@ mixin MailboxActionHandlerMixin { ..onCancelButtonAction(AppLocalizations.of(context).cancel, popBack)) .build() ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } @@ -160,6 +161,7 @@ mixin MailboxActionHandlerMixin { }) ..onCancelButtonAction(AppLocalizations.of(context).cancel, popBack) ).build()), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/base/mixin/message_dialog_action_mixin.dart b/lib/features/base/mixin/message_dialog_action_mixin.dart index 3756683cb4..b8d5673b68 100644 --- a/lib/features/base/mixin/message_dialog_action_mixin.dart +++ b/lib/features/base/mixin/message_dialog_action_mixin.dart @@ -21,7 +21,7 @@ mixin MessageDialogActionMixin { bool hasCancelButton = true, bool showAsBottomSheet = false, bool alignCenter = false, - bool outsideDismissible = true, + bool outsideDismissible = false, bool autoPerformPopBack = true, bool usePopScope = false, List? listTextSpan, diff --git a/lib/features/base/widget/drop_down_button_widget.dart b/lib/features/base/widget/drop_down_button_widget.dart index 57e9811c90..0492af8499 100644 --- a/lib/features/base/widget/drop_down_button_widget.dart +++ b/lib/features/base/widget/drop_down_button_widget.dart @@ -78,26 +78,29 @@ class DropDownButtonWidget extends StatelessWidget { items: items .map((item) => DropdownMenuItem( value: item, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: heightItem, - child: Row(children: [ - Expanded(child: Text(_getTextItemDropdown(context, item: item), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - if (supportSelectionIcon && item == itemSelected) - SvgPicture.asset(imagePaths.icChecked, - width: sizeIconChecked, - height: sizeIconChecked, - fit: BoxFit.fill) - ]), + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: heightItem, + child: Row(children: [ + Expanded(child: Text(_getTextItemDropdown(context, item: item), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + if (supportSelectionIcon && item == itemSelected) + SvgPicture.asset(imagePaths.icChecked, + width: sizeIconChecked, + height: sizeIconChecked, + fit: BoxFit.fill) + ]), + ), ), ), )) diff --git a/lib/features/base/widget/popup_item_no_icon_widget.dart b/lib/features/base/widget/popup_item_no_icon_widget.dart index 4fbea9c64f..03b91116e4 100644 --- a/lib/features/base/widget/popup_item_no_icon_widget.dart +++ b/lib/features/base/widget/popup_item_no_icon_widget.dart @@ -28,36 +28,39 @@ class PopupItemNoIconWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PointerInterceptor( - child: InkWell( - onTap: onCallbackAction, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - child: SizedBox( - width: maxWidth, - child: Row(children: [ - Expanded(child: Text( - _nameAction, - style: const TextStyle( - fontSize: 17, - color: Colors.black, - fontWeight: FontWeight.normal - ) - )), - if (isSelected && svgIconSelected != null) - ...[ - const SizedBox(width: 12), - SvgPicture.asset( - svgIconSelected!, - width: 24, - height: 24, - fit: BoxFit.fill - ), - ] - ]), - ), + return Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: InkWell( + onTap: onCallbackAction, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: SizedBox( + width: maxWidth, + child: Row(children: [ + Expanded(child: Text( + _nameAction, + style: const TextStyle( + fontSize: 17, + color: Colors.black, + fontWeight: FontWeight.normal + ) + )), + if (isSelected && svgIconSelected != null) + ...[ + const SizedBox(width: 12), + SvgPicture.asset( + svgIconSelected!, + width: 24, + height: 24, + fit: BoxFit.fill + ), + ] + ]), + ), + ) ) - ) + ), ); } } \ No newline at end of file diff --git a/lib/features/base/widget/popup_item_widget.dart b/lib/features/base/widget/popup_item_widget.dart index 169f51205f..03a14ebea6 100644 --- a/lib/features/base/widget/popup_item_widget.dart +++ b/lib/features/base/widget/popup_item_widget.dart @@ -33,38 +33,41 @@ class PopupItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PointerInterceptor( - child: InkWell( - onTap: onCallbackAction, - child: Container( - height: PopupItemWidgetStyle.height, - constraints: const BoxConstraints(minWidth: PopupItemWidgetStyle.minWidth), - padding: padding, - child: Row(children: [ - SvgPicture.asset( - _iconAction, - width: iconSize ?? PopupItemWidgetStyle.iconSize, - height: iconSize ?? PopupItemWidgetStyle.iconSize, - fit: BoxFit.fill, - colorFilter: colorIcon?.asFilter() - ), - const SizedBox(width: PopupItemWidgetStyle.space), - Expanded(child: Text( - _nameAction, - style: styleName ?? PopupItemWidgetStyle.labelTextStyle - )), - if (isSelected == true && selectedIcon != null) - Padding( - padding: PopupItemWidgetStyle.iconSelectedPadding, - child: SvgPicture.asset( - selectedIcon!, - width: PopupItemWidgetStyle.selectedIconSize, - height: PopupItemWidgetStyle.selectedIconSize, - fit: BoxFit.fill - ), - ) - ]), - ) + return Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: InkWell( + onTap: onCallbackAction, + child: Container( + height: PopupItemWidgetStyle.height, + constraints: const BoxConstraints(minWidth: PopupItemWidgetStyle.minWidth), + padding: padding, + child: Row(children: [ + SvgPicture.asset( + _iconAction, + width: iconSize ?? PopupItemWidgetStyle.iconSize, + height: iconSize ?? PopupItemWidgetStyle.iconSize, + fit: BoxFit.fill, + colorFilter: colorIcon?.asFilter() + ), + const SizedBox(width: PopupItemWidgetStyle.space), + Expanded(child: Text( + _nameAction, + style: styleName ?? PopupItemWidgetStyle.labelTextStyle + )), + if (isSelected == true && selectedIcon != null) + Padding( + padding: PopupItemWidgetStyle.iconSelectedPadding, + child: SvgPicture.asset( + selectedIcon!, + width: PopupItemWidgetStyle.selectedIconSize, + height: PopupItemWidgetStyle.selectedIconSize, + fit: BoxFit.fill + ), + ) + ]), + ) + ), ), ); } diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index b3ec9a78df..256bffd973 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -202,7 +202,7 @@ class ComposerController extends BaseController SignatureStatus _identityContentOnOpenPolicy = SignatureStatus.editedAvailable; int? _savedEmailDraftHash; bool _restoringSignatureButton = false; - + @visibleForTesting bool get restoringSignatureButton => _restoringSignatureButton; @@ -395,12 +395,6 @@ class ComposerController extends BaseController } }); }); - - if (richTextWebController != null) { - ever(richTextWebController!.formattingOptionsState, (_) { - richTextWebController!.editorController.setFocus(); - }); - } } void _triggerBrowserEventListener() { @@ -528,6 +522,7 @@ class ComposerController extends BaseController KeyEventResult _subjectEmailInputOnKeyListener(FocusNode node, KeyEvent event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { + subjectEmailInputFocusNode?.unfocus(); richTextWebController?.editorController.setFocus(); return KeyEventResult.handled; } @@ -1426,7 +1421,7 @@ class ComposerController extends BaseController final existedSignatureButton = emailDocument.querySelector( 'button.tmail-signature-button'); if (existedSignatureButton != null) return; - + final signature = emailDocument.querySelector('div.tmail-signature'); if (signature == null) return; _restoringSignatureButton = true; @@ -1908,7 +1903,6 @@ class ComposerController extends BaseController return false; } - void handleInitHtmlEditorWeb(String initContent) async { if (_isEmailBodyLoaded) return; log('ComposerController::handleInitHtmlEditorWeb:'); @@ -1931,12 +1925,12 @@ class ComposerController extends BaseController FocusManager.instance.primaryFocus?.unfocus(); subjectEmailInputController.popupWidget?.popupRenderer.dismiss(); richTextWebController?.editorController.setFocus(); + log('ComposerController::handleOnFocusHtmlEditorWeb:'); richTextWebController?.closeAllMenuPopup(); } - void handleOnMouseDownHtmlEditorWeb(BuildContext context) { - Navigator.maybePop(context); - FocusScope.of(context).unfocus(); + void handleOnMouseDownHtmlEditorWeb() { + log('ComposerController::handleOnMouseDownHtmlEditorWeb:'); _collapseAllRecipient(); _autoCreateEmailTag(); } diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 47c42d6123..8954bd68fc 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -424,6 +424,7 @@ class ComposerView extends GetWidget { List _createMoreOptionPopupItems(BuildContext context) { return [ PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icReadReceipt, @@ -440,6 +441,7 @@ class ComposerView extends GetWidget { ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icSaveToDraft, @@ -454,6 +456,7 @@ class ComposerView extends GetWidget { ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icDeleteMailbox, diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 6621e9f5f3..a3e292fb51 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -39,6 +39,7 @@ class ComposerView extends GetWidget { childBuilder: (context, constraints) { return GestureDetector( onTap: () => controller.clearFocus(context), + excludeFromSemantics: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -293,6 +294,7 @@ class ComposerView extends GetWidget { childBuilder: (context, constraints) { return GestureDetector( onTap: () => controller.clearFocus(context), + excludeFromSemantics: true, child: Column(children: [ Obx(() => DesktopAppBarComposerWidget( emailSubject: controller.subjectEmail.value ?? '', @@ -567,6 +569,7 @@ class ComposerView extends GetWidget { childBuilder: (context, constraints) { return GestureDetector( onTap: () => controller.clearFocus(context), + excludeFromSemantics: true, child: Column(children: [ Obx(() => DesktopAppBarComposerWidget( emailSubject: controller.subjectEmail.value ?? '', @@ -835,6 +838,7 @@ class ComposerView extends GetWidget { List _createMoreOptionPopupItems(BuildContext context) { return [ PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icStyleCodeView, @@ -851,6 +855,7 @@ class ComposerView extends GetWidget { ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icReadReceipt, @@ -867,6 +872,7 @@ class ComposerView extends GetWidget { ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icSaveToDraft, @@ -881,6 +887,7 @@ class ComposerView extends GetWidget { ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemWidget( controller.imagePaths.icDeleteMailbox, diff --git a/lib/features/composer/presentation/controller/rich_text_web_controller.dart b/lib/features/composer/presentation/controller/rich_text_web_controller.dart index 76f5cf473b..23bfa6553a 100644 --- a/lib/features/composer/presentation/controller/rich_text_web_controller.dart +++ b/lib/features/composer/presentation/controller/rich_text_web_controller.dart @@ -25,6 +25,7 @@ class RichTextWebController extends BaseRichTextController { static const List fontSizeList = [10, 12, 14, 15, 16, 18, 24, 36, 48, 64]; static const int fontSizeDefault = 16; + static const int _applyStyleDelayTimeMilliseconds = 500; final editorController = HtmlEditorController(); @@ -151,8 +152,8 @@ class RichTextWebController extends BaseRichTextController { openMenuSelectColor( context, selectedTextBackgroundColor.value, - onResetToDefault: () => applyBackgroundColor(Colors.white), - onSelectColor: applyBackgroundColor + onResetToDefault: () => _applyBackgroundColor(Colors.white), + onSelectColor: _applyBackgroundColor ); break; default: @@ -162,7 +163,7 @@ class RichTextWebController extends BaseRichTextController { } } - void _applyForegroundColor(Color? selectedColor) { + Future _applyForegroundColor(Color? selectedColor) async { final newColor = selectedColor ?? Colors.black; final colorAsString = newColor.toHexTriplet(); log('RichTextWebController::_applyForegroundColor():colorAsString: $colorAsString'); @@ -170,9 +171,10 @@ class RichTextWebController extends BaseRichTextController { editorController.execSummernoteAPI( RichTextStyleType.textColor.summernoteNameAPI, value: colorAsString); + await _requestEditorFocus(); } - void applyBackgroundColor(Color? selectedColor) { + Future _applyBackgroundColor(Color? selectedColor) async { final newColor = selectedColor ?? Colors.white; final colorAsString = newColor.toHexTriplet(); log('RichTextWebController::_applyBackgroundColor():colorAsString: $colorAsString'); @@ -180,6 +182,7 @@ class RichTextWebController extends BaseRichTextController { editorController.execSummernoteAPI( RichTextStyleType.textBackgroundColor.summernoteNameAPI, value: colorAsString); + await _requestEditorFocus(); } void _selectTextStyleType(RichTextStyleType textStyleType) { @@ -197,17 +200,19 @@ class RichTextWebController extends BaseRichTextController { editorController.insertHtml("
${inlineImage.base64Uri ?? ''}

"); } - void applyNewFontStyle(FontNameType? newFont) { + Future applyNewFontStyle(FontNameType? newFont) async { final fontSelected = newFont ?? FontNameType.sansSerif; selectedFontName.value = fontSelected; editorController.execSummernoteAPI( RichTextStyleType.fontName.summernoteNameAPI, value: fontSelected.value); + await _requestEditorFocus(); } - void applyNewFontSize(int? newSize) { + Future applyNewFontSize(int? newSize) async { selectedFontSize.value = newSize ?? fontSizeDefault; editorController.setFontSize(newSize ?? fontSizeDefault); + await _requestEditorFocus(); } bool get isMenuFontOpen => menuFontStatus.value == DropdownMenuFontStatus.open; @@ -240,22 +245,23 @@ class RichTextWebController extends BaseRichTextController { editorController.toggleCodeView(); } - void applyHeaderStyle(HeaderStyleType? newStyle) { + Future applyHeaderStyle(HeaderStyleType? newStyle) async { final styleSelected = newStyle ?? HeaderStyleType.normal; if (styleSelected == HeaderStyleType.blockquote || styleSelected == HeaderStyleType.code) { editorController.execCommand( RichTextStyleType.headerStyle.commandAction, argument: styleSelected.styleValue); - editorController.setFocus(); } else { editorController.execSummernoteAPI(styleSelected.summernoteNameAPI); } + await _requestEditorFocus(); } - void applyParagraphType(ParagraphType newParagraph) { + Future applyParagraphType(ParagraphType newParagraph) async { selectedParagraph.value = newParagraph; editorController.execSummernoteAPI(newParagraph.summernoteNameAPI); menuParagraphController.hideMenu(); + await _requestEditorFocus(); } void closeAllMenuPopup() { @@ -273,10 +279,11 @@ class RichTextWebController extends BaseRichTextController { } } - void applyOrderListType(OrderListType newOrderList) { + Future applyOrderListType(OrderListType newOrderList) async { selectedOrderList.value = newOrderList; editorController.execSummernoteAPI(newOrderList.summernoteNameAPI); menuOrderListController.hideMenu(); + await _requestEditorFocus(); } void insertImageAsBase64({required PlatformFile platformFile, int? maxWidth}) { @@ -296,10 +303,20 @@ class RichTextWebController extends BaseRichTextController { : FormattingOptionsState.enabled; formattingOptionsState.value = newState; + + FocusManager.instance.primaryFocus?.unfocus(); + editorController.setFocus(); } bool get isFormattingOptionsEnabled => formattingOptionsState.value == FormattingOptionsState.enabled; + Future _requestEditorFocus() async { + await Future.delayed( + const Duration(milliseconds: _applyStyleDelayTimeMilliseconds) + ); + editorController.setFocus(); + } + @override void onClose() { menuParagraphController.dispose(); 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 484c277cf2..bab9a65cf9 100644 --- a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart +++ b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart @@ -113,6 +113,7 @@ mixin RichTextButtonMixin { Color? color, String? tooltip, double opacity = 1.0, + bool excludeFromSemantics = false, }){ final newColor = color == Colors.white ? AppColor.colorDefaultRichTextButton @@ -123,6 +124,7 @@ mixin RichTextButtonMixin { message: tooltip, child: SvgPicture.asset( path, + excludeFromSemantics: excludeFromSemantics, colorFilter: newColor?.withOpacity(opacity).asFilter(), fit: BoxFit.fill)) : SvgPicture.asset( @@ -240,14 +242,14 @@ mixin RichTextButtonMixin { : DropdownMenuFontStatus.closed; richTextController.menuHeaderStyleStatus.value = newStatus; }, - onChanged: (newStyle) => richTextController.applyHeaderStyle(newStyle)), + onChanged: richTextController.applyHeaderStyle), Container( width: 130, padding: const EdgeInsets.only(left: 4.0, right: 4.0), child: DropDownButtonWidget( items: FontNameType.values, itemSelected: richTextController.selectedFontName.value, - onChanged: (newFont) => richTextController.applyNewFontStyle(newFont), + onChanged: richTextController.applyNewFontStyle, onMenuStateChange: (isOpen) { final newStatus = isOpen ? DropdownMenuFontStatus.open @@ -271,6 +273,7 @@ mixin RichTextButtonMixin { path: RichTextStyleType.textColor.getIcon(_imagePaths), color: richTextController.selectedTextColor.value, tooltip: RichTextStyleType.textColor.getTooltipButton(context), + excludeFromSemantics: true ), onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.textColor)), ), @@ -322,7 +325,7 @@ mixin RichTextButtonMixin { .map((paragraph) => paragraph.buildButtonWidget( context, _imagePaths, - (paragraph) => richTextController.applyParagraphType(paragraph))) + richTextController.applyParagraphType)) .toList(), iconButton: buildWrapIconStyleText( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), @@ -331,7 +334,8 @@ mixin RichTextButtonMixin { icon: buildIconWithTooltip( path: richTextController.selectedParagraph.value.getIcon(_imagePaths), color: AppColor.colorDefaultRichTextButton, - tooltip: RichTextStyleType.paragraph.getTooltipButton(context))), + tooltip: RichTextStyleType.paragraph.getTooltipButton(context), + excludeFromSemantics: true)), ), ), Padding( @@ -342,7 +346,7 @@ mixin RichTextButtonMixin { .map((orderType) => orderType.buildButtonWidget( context, _imagePaths, - (orderType) => richTextController.applyOrderListType(orderType))) + richTextController.applyOrderListType)) .toList(), iconButton: buildWrapIconStyleText( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), @@ -351,7 +355,8 @@ mixin RichTextButtonMixin { icon: buildIconWithTooltip( path: richTextController.selectedOrderList.value.getIcon(_imagePaths), color: AppColor.colorDefaultRichTextButton, - tooltip: RichTextStyleType.orderList.getTooltipButton(context))), + tooltip: RichTextStyleType.orderList.getTooltipButton(context), + excludeFromSemantics: true)), ), ) ]; diff --git a/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart b/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart index 357dd4a811..c989590776 100644 --- a/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart +++ b/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart @@ -34,12 +34,15 @@ class DropDownMenuHeaderStyleWidget extends StatelessWidget { items: items .map((item) => DropdownMenuItem( value: item, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: heightItem, - alignment: Alignment.centerLeft, - child: _buildItemDropdown(item), + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: heightItem, + alignment: Alignment.centerLeft, + child: _buildItemDropdown(item), + ), ), ), )) diff --git a/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart b/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart index 4dbec500b4..29c3dc35dd 100644 --- a/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart +++ b/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart @@ -68,57 +68,60 @@ class FromComposerDropDownWidget extends StatelessWidget { isExpanded: false, items: items.map((item) => DropdownMenuItem( value: item, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - padding: FromComposerDropDownWidgetStyle.dropdownItemPadding, - child: Row( - children: [ - Container( - width: FromComposerDropDownWidgetStyle.avatarSize, - height: FromComposerDropDownWidgetStyle.avatarSize, - alignment: Alignment.center, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.avatarColor, - border: Border.all( - color: AppColor.colorShadowBgContentEmail, - width: FromComposerDropDownWidgetStyle.avatarBorderWidth - ) - ), - child: Text( - item.name!.isNotEmpty - ? item.name!.firstLetterToUpperCase - : item.email!.firstLetterToUpperCase, - style: FromComposerDropDownWidgetStyle.avatarTextStyle, - ), - ), - const SizedBox(width: FromComposerDropDownWidgetStyle.dropdownItemSpace), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (item.name!.isNotEmpty) - Text( - item.name!, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - style: FromComposerDropDownWidgetStyle.dropdownItemTitleTextStyle, - ), - if (item.email!.isNotEmpty) - Text( - item.email!, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - style: FromComposerDropDownWidgetStyle.dropdownItemSubTitleTextStyle, - ) - ], + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + padding: FromComposerDropDownWidgetStyle.dropdownItemPadding, + child: Row( + children: [ + Container( + width: FromComposerDropDownWidgetStyle.avatarSize, + height: FromComposerDropDownWidgetStyle.avatarSize, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.avatarColor, + border: Border.all( + color: AppColor.colorShadowBgContentEmail, + width: FromComposerDropDownWidgetStyle.avatarBorderWidth + ) + ), + child: Text( + item.name!.isNotEmpty + ? item.name!.firstLetterToUpperCase + : item.email!.firstLetterToUpperCase, + style: FromComposerDropDownWidgetStyle.avatarTextStyle, + ), ), - ) - ], + const SizedBox(width: FromComposerDropDownWidgetStyle.dropdownItemSpace), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (item.name!.isNotEmpty) + Text( + item.name!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerDropDownWidgetStyle.dropdownItemTitleTextStyle, + ), + if (item.email!.isNotEmpty) + Text( + item.email!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerDropDownWidgetStyle.dropdownItemSubTitleTextStyle, + ) + ], + ), + ) + ], + ), ), ), ), diff --git a/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart b/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart index 576bea5e8d..3effc35f9a 100644 --- a/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart +++ b/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart @@ -21,26 +21,29 @@ class ItemMenuFontSizeWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PointerInterceptor( - child: Container( - alignment: Alignment.center, - color: Colors.transparent, - child: Stack( + return Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( alignment: Alignment.center, - children: [ - Text( - '$value', - style: ItemMenuFontSizeWidgetStyle.labelTextStyle, - ), - if (value == selectedValue) - Align( - alignment: AlignmentDirectional.centerStart, - child: Padding( - padding: ItemMenuFontSizeWidgetStyle.selectIconPadding, - child: SvgPicture.asset(_imagePaths.icSelectedSB), + color: Colors.transparent, + child: Stack( + alignment: Alignment.center, + children: [ + Text( + '$value', + style: ItemMenuFontSizeWidgetStyle.labelTextStyle, + ), + if (value == selectedValue) + Align( + alignment: AlignmentDirectional.centerStart, + child: Padding( + padding: ItemMenuFontSizeWidgetStyle.selectIconPadding, + child: SvgPicture.asset(_imagePaths.icSelectedSB), + ) ) - ) - ], + ], + ), ), ), ); diff --git a/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart b/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart index 8e98cb961a..5cbb4068fa 100644 --- a/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart +++ b/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart @@ -91,7 +91,7 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin child: DropDownButtonWidget( items: FontNameType.values, itemSelected: richTextWebController.selectedFontName.value, - onChanged: (newFont) => richTextWebController.applyNewFontStyle(newFont), + onChanged: richTextWebController.applyNewFontStyle, onMenuStateChange: (isOpen) { final newStatus = isOpen ? DropdownMenuFontStatus.open @@ -119,7 +119,8 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin path: RichTextStyleType.textColor.getIcon(_imagePaths), color: richTextWebController.selectedTextColor.value, tooltip: RichTextStyleType.textColor.getTooltipButton(context), - opacity: opacity + opacity: opacity, + excludeFromSemantics: true ), onTap: () => richTextWebController.applyRichTextStyle( context, @@ -210,7 +211,7 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin .map((paragraph) => paragraph.buildButtonWidget( context, _imagePaths, - (paragraph) => richTextWebController.applyParagraphType(paragraph))) + richTextWebController.applyParagraphType)) .toList(), iconButton: buildWrapIconStyleText( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), @@ -220,6 +221,7 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin path: richTextWebController.selectedParagraph.value.getIcon(_imagePaths), color: AppColor.colorDefaultRichTextButton, opacity: opacity, + excludeFromSemantics: true, tooltip: RichTextStyleType.paragraph.getTooltipButton(context) ) ), @@ -233,7 +235,7 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin .map((orderType) => orderType.buildButtonWidget( context, _imagePaths, - (orderType) => richTextWebController.applyOrderListType(orderType))) + richTextWebController.applyOrderListType)) .toList(), iconButton: buildWrapIconStyleText( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), @@ -243,6 +245,7 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin path: richTextWebController.selectedOrderList.value.getIcon(_imagePaths), color: AppColor.colorDefaultRichTextButton, opacity: opacity, + excludeFromSemantics: true, tooltip: RichTextStyleType.orderList.getTooltipButton(context) ) ), diff --git a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart index 83dc646f58..e64e377ca5 100644 --- a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart +++ b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart @@ -9,7 +9,7 @@ import 'package:universal_html/html.dart' hide VoidCallback; typedef OnChangeContentEditorAction = Function(String? text); typedef OnInitialContentEditorAction = Function(String text); -typedef OnMouseDownEditorAction = Function(BuildContext context); +typedef OnMouseDownEditorAction = Function(); typedef OnEditorSettingsChange = Function(EditorSettings settings); typedef OnEditorTextSizeChanged = Function(int? size); typedef OnDragEnterListener = Function(List? types); @@ -65,13 +65,9 @@ class WebEditorWidget extends StatefulWidget { class _WebEditorState extends State { - static const double _offsetHeight = 50; - static const double _offsetWidth = 90; static const double _defaultHtmlEditorHeight = 550; late HtmlEditorController _editorController; - double? dropZoneWidth; - double? dropZoneHeight; final ValueNotifier _htmlEditorHeight = ValueNotifier(_defaultHtmlEditorHeight); bool _dropListenerRegistered = false; Function(Event)? _dropListener; @@ -82,14 +78,8 @@ class _WebEditorState extends State { _editorController = widget.editorController; log('_WebEditorState::initState:height: ${widget.height} | width: ${widget.width}'); if (widget.height != null) { - dropZoneHeight = widget.height! - _offsetHeight; _htmlEditorHeight.value = widget.height ?? _defaultHtmlEditorHeight; } - if (widget.width != null) { - dropZoneWidth = widget.width! - _offsetWidth; - } - log('_WebEditorState::initState:dropZoneWidth: $dropZoneWidth | dropZoneHeight: $dropZoneHeight'); - _dropListener = (event) { if (event is MessageEvent) { if (jsonDecode(event.data)['name'] == HtmlUtils.registerDropListener.name) { @@ -181,8 +171,8 @@ class _WebEditorState extends State { } }, onFocus: widget.onFocus, - onBlur: widget.onUnFocus, - onMouseDown: () => widget.onMouseDown?.call(context), + onUnFocus: widget.onUnFocus, + onMouseDown: widget.onMouseDown, onChangeSelection: widget.onEditorSettings, onChangeCodeview: widget.onChangeContent, onTextFontSizeChanged: widget.onEditorTextSizeChanged, diff --git a/lib/features/contact/presentation/contact_view.dart b/lib/features/contact/presentation/contact_view.dart index ae1da616f2..3f7b97774e 100644 --- a/lib/features/contact/presentation/contact_view.dart +++ b/lib/features/contact/presentation/contact_view.dart @@ -1,4 +1,5 @@ import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -22,7 +23,9 @@ class ContactView extends GetWidget { backgroundColor: Colors.black38, body: PointerInterceptor( child: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isWeb + ? null + : FocusScope.of(context).unfocus, child: SafeArea( bottom: false, left: false, diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 2b9e09d765..4f44104257 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -1143,6 +1143,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { onQuickCreatingRuleEmailDialogAction: (emailAddress) => quickCreatingRule(context, emailAddress) ) ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } @@ -1189,6 +1190,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack())) .build() ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } @@ -1515,6 +1517,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ) ), barrierColor: AppColor.colorDefaultCupertinoActionSheet, + barrierDismissible: false ); } } diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 507ac40c6c..b847d0bdbb 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -558,6 +558,7 @@ class EmailView extends GetWidget { ) { return actionTypes.map((action) { return PopupMenuItem( + enabled: false, key: Key('${action.name}_action'), padding: EdgeInsets.zero, child: PopupItemWidget( diff --git a/lib/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart b/lib/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart index 90200464aa..6602e0517b 100644 --- a/lib/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart +++ b/lib/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart @@ -211,6 +211,7 @@ class EmailAddressBottomSheetBuilder { shape: _shape(), isScrollControlled: true, enableDrag: false, + isDismissible: false, backgroundColor: Colors.transparent ); } diff --git a/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart b/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart index efbc919b1f..4aade8fb0a 100644 --- a/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart +++ b/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart @@ -1,5 +1,4 @@ import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/views/checkbox/labeled_checkbox.dart'; import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger_mobile.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -29,13 +28,21 @@ class CheckBoxHasAttachmentWidget extends StatelessWidget { nextFocusNode?.requestFocus(); } }, - child: LabeledCheckbox( - label: AppLocalizations.of(context).hasAttachment, - focusNode: currentFocusNode, - contentPadding: EdgeInsets.zero, + child: CheckboxListTile( value: hasAttachmentValue, + contentPadding: const EdgeInsetsDirectional.only(start: 4), activeColor: AppColor.primaryColor, - onChanged: onChanged, + focusNode: currentFocusNode, + controlAffinity: ListTileControlAffinity.leading, + onChanged: onChanged, + title: Text( + AppLocalizations.of(context).hasAttachment, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + ), ) ); } diff --git a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart index 32cc96f31d..7fc9cc36c1 100644 --- a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart +++ b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart @@ -108,29 +108,32 @@ class DateSelectionDropDownButton extends StatelessWidget { ) { return DropdownMenuItem( value: recoveryTime, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: DateSelectionDropdownButtonStyles.height, - child: Row( - children: [ - Expanded( - child: Text( - recoveryTime.getTitle(context), - style: DateSelectionDropdownButtonStyles.selectedValueTexStyle, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - ) - ), - if (recoveryTime == recoveryTimeSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: DateSelectionDropdownButtonStyles.checkedIconSize, - height: DateSelectionDropdownButtonStyles.checkedIconSize, - fit: BoxFit.fill - ) - ] + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: DateSelectionDropdownButtonStyles.height, + child: Row( + children: [ + Expanded( + child: Text( + recoveryTime.getTitle(context), + style: DateSelectionDropdownButtonStyles.selectedValueTexStyle, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + ) + ), + if (recoveryTime == recoveryTimeSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: DateSelectionDropdownButtonStyles.checkedIconSize, + height: DateSelectionDropdownButtonStyles.checkedIconSize, + fit: BoxFit.fill + ) + ] + ), ), ), ), diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 62b9027108..8ca940b709 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -842,6 +842,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM popBack())) .build() ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/mailbox/presentation/mailbox_view_web.dart b/lib/features/mailbox/presentation/mailbox_view_web.dart index 60c54f8b99..4687ddfbda 100644 --- a/lib/features/mailbox/presentation/mailbox_view_web.dart +++ b/lib/features/mailbox/presentation/mailbox_view_web.dart @@ -299,6 +299,7 @@ class MailboxView extends BaseMailboxView { parent: Obx(() => MailboxItemWidget( mailboxNode: mailboxNode, mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value, + itemExcludeFromSemantics: true, onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item), onExpandFolderActionClick: (mailboxNode) => controller.toggleMailboxFolder(mailboxNode, controller.mailboxListScrollController), onSelectMailboxFolderClick: controller.selectMailboxNode, @@ -321,6 +322,7 @@ class MailboxView extends BaseMailboxView { return Obx(() => MailboxItemWidget( mailboxNode: mailboxNode, mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value, + itemExcludeFromSemantics: true, onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item), onSelectMailboxFolderClick: controller.selectMailboxNode, onDragItemAccepted: _handleDragItemAccepted, diff --git a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart index cd49df0eae..6e67920f4d 100644 --- a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart +++ b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart @@ -258,6 +258,7 @@ mixin MailboxWidgetMixin { } ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: AbsorbPointer( absorbing: !contextMenuItem.isActivated, diff --git a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart index dba008eb3b..9af79016eb 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart @@ -25,6 +25,7 @@ class MailboxItemWidget extends StatefulWidget { final PresentationMailbox? mailboxNodeSelected; final MailboxActions? mailboxActions; final MailboxId? mailboxIdAlreadySelected; + final bool itemExcludeFromSemantics; final OnClickExpandMailboxNodeAction? onExpandFolderActionClick; final OnClickOpenMailboxNodeAction? onOpenMailboxFolderClick; @@ -39,6 +40,7 @@ class MailboxItemWidget extends StatefulWidget { required this.mailboxNode, this.selectionMode = SelectMode.INACTIVE, this.mailboxDisplayed = MailboxDisplayed.mailbox, + this.itemExcludeFromSemantics = false, this.mailboxNodeSelected, this.mailboxActions, this.mailboxIdAlreadySelected, @@ -70,6 +72,7 @@ class _MailboxItemWidgetState extends State { return InkWell( onTap: () => widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), onHover: (value) => setState(() => _isItemHovered = value), + excludeFromSemantics: widget.itemExcludeFromSemantics, child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(MailboxItemWidgetStyles.borderRadius)), @@ -111,6 +114,7 @@ class _MailboxItemWidgetState extends State { return InkWell( onTap: () => widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), onHover: (value) => setState(() => _isItemHovered = value), + excludeFromSemantics: widget.itemExcludeFromSemantics, child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(MailboxItemWidgetStyles.borderRadius)), @@ -151,6 +155,7 @@ class _MailboxItemWidgetState extends State { onTap: () => widget.selectionMode == SelectMode.ACTIVE ? widget.onSelectMailboxFolderClick?.call(widget.mailboxNode) : widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), + excludeFromSemantics: widget.itemExcludeFromSemantics, borderRadius: const BorderRadius.all(Radius.circular(MailboxItemWidgetStyles.borderRadius)), child: Container( decoration: BoxDecoration( @@ -210,6 +215,7 @@ class _MailboxItemWidgetState extends State { onTap: () => !_isSelectActionNoValid ? widget.onOpenMailboxFolderClick?.call(widget.mailboxNode) : null, + excludeFromSemantics: widget.itemExcludeFromSemantics, customBorder: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8))), hoverColor: AppColor.colorMailboxHovered, child: Container( diff --git a/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart b/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart index f6c1229226..9f67109e44 100644 --- a/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart +++ b/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart @@ -1,7 +1,6 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/style_utils.dart'; import 'package:core/presentation/views/text/text_field_builder.dart'; -import 'package:core/utils/app_logger.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; @@ -25,7 +24,6 @@ class MailboxCreatorView extends GetWidget { @override Widget build(BuildContext context) { - log('MailboxCreatorView::build():'); return PointerInterceptor( child: GestureDetector( onTap: () => controller.closeMailboxCreator(context), @@ -40,7 +38,9 @@ class MailboxCreatorView extends GetWidget { right: false, child: Center( child: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isMobile + ? FocusScope.of(context).unfocus + : () {}, child: Container( margin: _getMarginView(context), decoration: BoxDecoration( @@ -60,20 +60,18 @@ class MailboxCreatorView extends GetWidget { ]), width: _getWidthView(context), height: _getHeightView(context), - child: ClipRRect( - borderRadius: _getRadiusView(context), - child: SafeArea( - top: false, - bottom: false, - left: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), - right: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), - child: Column(children: [ - _buildAppBar(context), - const Divider(color: AppColor.colorDividerDestinationPicker, height: 1), - _buildCreateMailboxNameInput(context), - _buildMailboxLocation(context), - ]), - ) + clipBehavior: Clip.antiAlias, + child: SafeArea( + top: false, + bottom: false, + left: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), + right: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), + child: Column(children: [ + _buildAppBar(context), + const Divider(color: AppColor.colorDividerDestinationPicker, height: 1), + _buildCreateMailboxNameInput(context), + _buildMailboxLocation(context), + ]), ) ), ) diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 23191f91cf..8dfe8faa8c 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -1297,6 +1297,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack())) .build() ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } @@ -2400,6 +2401,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo }) ..onCancelButtonAction(AppLocalizations.of(context).cancel, popBack) ).build()), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart index 65f6d88836..b2dc797813 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart @@ -227,6 +227,10 @@ class SearchController extends BaseController with DateRangePickerMixin { } void clearTextSearch() { + FocusManager.instance.primaryFocus?.unfocus(); + if (isAdvancedSearchViewOpen.isTrue) { + closeAdvanceSearch(); + } searchInputController.clear(); searchFocus.requestFocus(); } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 9b3e329e20..ee17026631 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -279,42 +279,27 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { if (controller.emailsInCurrentMailbox.isEmpty) { return const SizedBox.shrink(); } else { - return Padding( - padding: const EdgeInsetsDirectional.only(start: 16), - child: Tooltip( - message: AppLocalizations.of(context).selectAllMessagesOfThisPage, - child: ElevatedButton.icon( - onPressed: controller.selectAllEmailAction, - icon: SvgPicture.asset( - controller.imagePaths.icSelectAll, - width: 16, - height: 16, - fit: BoxFit.fill, - colorFilter: AppColor.colorFilterMessageIcon.asFilter(), - ), - label: Text( - AppLocalizations.of(context).selectAllMessagesOfThisPage, - maxLines: 1, - overflow: TextOverflow.ellipsis - ), - style: ElevatedButton.styleFrom( - backgroundColor: AppColor.colorFilterMessageButton.withOpacity(0.6), - shadowColor: Colors.transparent, - padding: const EdgeInsetsDirectional.symmetric(horizontal: 12, vertical: 8), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - elevation: 0.0, - foregroundColor: AppColor.colorTextButtonHeaderThread, - maximumSize: const Size.fromWidth(250), - textStyle: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.normal, - color: AppColor.colorFilterMessageTitle - ), - ), - ), - ), + return TMailButtonWidget( + key: const Key('select_all_emails_button'), + text: AppLocalizations.of(context).selectAllMessagesOfThisPage, + maxLines: 1, + backgroundColor: AppColor.colorButtonHeaderThread, + maxWidth: 270, + textStyle: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + color: AppColor.colorTextButtonHeaderThread), + icon: controller.imagePaths.icSelectAll, + iconSize: 16, + borderRadius: 10, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 12, + vertical: 8), + flexibleText: true, + mainAxisSize: MainAxisSize.min, + margin: const EdgeInsetsDirectional.only(start: 16), + tooltipMessage: AppLocalizations.of(context).selectAllMessagesOfThisPage, + onTapActionCallback: controller.selectAllEmailAction, ); } }), @@ -618,6 +603,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ) { return EmailReceiveTimeType.values .map((receiveTime) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemNoIconWidget( receiveTime.getTitle(context), @@ -636,6 +622,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ) { return EmailSortOrderType.values .map((sortType) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemNoIconWidget( sortType.getTitle(context), diff --git a/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart b/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart index 53b1bada55..96058f6c83 100644 --- a/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart +++ b/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart @@ -21,6 +21,7 @@ mixin FilterEmailPopupMenuMixin { if (!isSearchEmailRunning) ...[ PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _filterEmailAction( context, @@ -30,6 +31,7 @@ mixin FilterEmailPopupMenuMixin { const PopupMenuDivider(height: 0.5) ], PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _filterEmailAction( context, @@ -38,6 +40,7 @@ mixin FilterEmailPopupMenuMixin { onCallBack)), const PopupMenuDivider(height: 0.5), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _filterEmailAction( context, diff --git a/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart b/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart index 08b5592cd6..ac65b0f189 100644 --- a/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart +++ b/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart @@ -43,6 +43,7 @@ mixin UserSettingPopupMenuMixin { ...[ const PopupMenuDivider(height: 0.5), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _settingAction(context, onSettingAction) ), @@ -52,6 +53,7 @@ mixin UserSettingPopupMenuMixin { ...[ const PopupMenuDivider(height: 0.5), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _logoutAction(context, onLogoutAction) ), diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart index 3ed62d63c4..5ba6062f17 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart @@ -1,10 +1,10 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/icon_button_web.dart'; -import 'package:core/presentation/views/checkbox/labeled_checkbox.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -113,25 +113,12 @@ class AdvancedSearchFilterFormBottomView extends GetWidget KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (event) { - if (event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.tab) { - nextFocusNode?.requestFocus(); - } - }, - child: LabeledCheckbox( - label: AppLocalizations.of(context).hasAttachment, - focusNode: currentFocusNode, - contentPadding: EdgeInsets.zero, - value: controller.hasAttachment.value, - activeColor: AppColor.primaryColor, - onChanged: controller.onHasAttachmentCheckboxChanged, - ), - ), - ); + return Obx(() => CheckBoxHasAttachmentWidget( + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, + onChanged: controller.onHasAttachmentCheckboxChanged, + hasAttachmentValue: controller.hasAttachment.value, + )); } Widget _buildButton({ diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart index e997a8c114..310a446eeb 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart @@ -11,32 +11,29 @@ class AdvancedSearchFilterOverlay extends StatelessWidget { @override Widget build(BuildContext context) { return PointerInterceptor( - child: GestureDetector( - onTap: () => FocusManager.instance.primaryFocus?.unfocus(), - child: Container( - constraints: BoxConstraints( - maxHeight: _getHeightOverlay(context), - ), - margin: const EdgeInsetsDirectional.only(top: 4, bottom: 16, end: 22), - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: const [ - BoxShadow( - color: AppColor.colorShadowComposer, - blurRadius: 32, - offset: Offset.zero), - BoxShadow( - color: AppColor.colorDropShadow, - blurRadius: 4, - offset: Offset.zero), - ] - ), - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), - child: AdvancedSearchInputForm(), - ), + child: Container( + constraints: BoxConstraints( + maxHeight: _getHeightOverlay(context), + ), + margin: const EdgeInsetsDirectional.only(top: 4, bottom: 16, end: 22), + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: const [ + BoxShadow( + color: AppColor.colorShadowComposer, + blurRadius: 32, + offset: Offset.zero), + BoxShadow( + color: AppColor.colorDropShadow, + blurRadius: 4, + offset: Offset.zero), + ] + ), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), + child: AdvancedSearchInputForm(), ), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart index 9a043c921e..63867bb701 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart @@ -134,6 +134,7 @@ class AdvancedSearchInputForm extends GetWidget List _buildEmailReceiveTimeTypeActionTiles(BuildContext context) { return EmailReceiveTimeType.values .map((receiveTime) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemNoIconWidget( receiveTime.getTitle(context), diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart index e786dde2be..92eae0a4cb 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart @@ -106,30 +106,33 @@ class DateDropDownButton extends StatelessWidget { ) { return DropdownMenuItem( value: receiveTime, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: 44, - child: Row(children: [ - Expanded(child: Text( - receiveTime.getTitle(context), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - if (receiveTime == receiveTimeSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: 20, - height: 20, - fit: BoxFit.fill - ) - ]), + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: 44, + child: Row(children: [ + Expanded(child: Text( + receiveTime.getTitle(context), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + if (receiveTime == receiveTimeSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: 20, + height: 20, + fit: BoxFit.fill + ) + ]), + ), ), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart index 40318b6d18..b30ee7943c 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart @@ -38,9 +38,16 @@ class IconOpenAdvancedSearchWidget extends StatelessWidget { width: 16, height: 16), onTap: () { - log('IconOpenAdvancedSearchWidget::build(): clicked'); - advancedFilterController.initSearchFilterField(context); - searchController.openAdvanceSearch(); + log('IconOpenAdvancedSearchWidget::build:onTap: isAdvancedSearchViewOpen = ${searchController.isAdvancedSearchViewOpen}'); + log('IconOpenAdvancedSearchWidget::build:onTap: advancedSearchIsActivated = ${searchController.advancedSearchIsActivated}'); + FocusScope.of(context).unfocus(); + + if (searchController.isAdvancedSearchViewOpen.isTrue) { + searchController.closeAdvanceSearch(); + } else { + advancedFilterController.initSearchFilterField(context); + searchController.openAdvanceSearch(); + } }), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart index ef591f99f6..3f81aa441c 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart @@ -32,31 +32,34 @@ class SortByDropDownButton extends StatelessWidget { items: EmailSortOrderType.values .map((sortType) => DropdownMenuItem( value: sortType, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: SortByDropdownStyle.height, - child: Row( - children: [ - Expanded( - child: Text( - sortType.getTitle(context), - style: sortType.getTextStyle(isInDropdown: true), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: SortByDropdownStyle.height, + child: Row( + children: [ + Expanded( + child: Text( + sortType.getTitle(context), + style: sortType.getTextStyle(isInDropdown: true), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + ), ), - ), - if (sortType == sortOrderSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: SortByDropdownStyle.checkedIconSize, - height: SortByDropdownStyle.checkedIconSize, - fit: BoxFit.fill, - ) - ], - ), - ) + if (sortType == sortOrderSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: SortByDropdownStyle.checkedIconSize, + height: SortByDropdownStyle.checkedIconSize, + fit: BoxFit.fill, + ) + ], + ), + ) + ), ) )).toList(), value: sortOrderSelected, diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart index 5d393bbedf..504b17f1bf 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart @@ -28,6 +28,7 @@ class AppGridDashboardIcon extends StatelessWidget { visible: isAppGridOpen, portalFollower: GestureDetector( behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, onTap: appGridController.toggleAppGridDashboard ), child: PortalTarget( diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart index 90be1e505f..ddde3c3e3c 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart @@ -43,6 +43,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { portalFollower: PointerInterceptor( child: GestureDetector( behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, onTap: _searchController.closeAdvanceSearch ), ), diff --git a/lib/features/manage_account/presentation/email_rules/email_rules_controller.dart b/lib/features/manage_account/presentation/email_rules/email_rules_controller.dart index b89bfad0ab..caf778e2fe 100644 --- a/lib/features/manage_account/presentation/email_rules/email_rules_controller.dart +++ b/lib/features/manage_account/presentation/email_rules/email_rules_controller.dart @@ -187,6 +187,7 @@ class EmailRulesController extends BaseController { popBack())) .build() ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart b/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart index 0de0089de9..7856ea389c 100644 --- a/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart +++ b/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart @@ -13,9 +13,7 @@ class LanguageAndRegionController extends BaseController { final SaveLanguageInteractor _saveLanguageInteractor; - final listSupportedLanguages = [].obs; final languageSelected = LocalizationService.defaultLocale.obs; - final isLanguageMenuOverlayOpen = RxBool(false); LanguageAndRegionController(this._saveLanguageInteractor); @@ -34,8 +32,6 @@ class LanguageAndRegionController extends BaseController { } void _setUpSupportedLanguages() { - listSupportedLanguages.value = LocalizationService.supportedLocales; - final currentLocale = Get.locale; log('LanguageAndRegionController::_setUpSupportedLanguages():currentLocale: $currentLocale'); if (currentLocale != null) { @@ -46,7 +42,6 @@ class LanguageAndRegionController extends BaseController { } void selectLanguage(Locale? selectedLocale) { - isLanguageMenuOverlayOpen.value = false; languageSelected.value = selectedLocale ?? LocalizationService.defaultLocale; _saveLanguage(languageSelected.value); } @@ -54,8 +49,4 @@ class LanguageAndRegionController extends BaseController { void _saveLanguage(Locale localeCurrent) { consumeState(_saveLanguageInteractor.execute(localeCurrent)); } - - void toggleLanguageMenuOverlay() { - isLanguageMenuOverlayOpen.toggle(); - } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart b/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart index 8bc230254f..5707858254 100644 --- a/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart +++ b/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart @@ -1,11 +1,13 @@ import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_and_region_header_widget.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; @@ -50,7 +52,42 @@ class LanguageAndRegionView extends GetWidget { children: [ const LanguageAndRegionHeaderWidget(), const SizedBox(height: 22), - Expanded(child: ChangeLanguageButtonWidget()) + Expanded( + child: LayoutBuilder(builder: (context, constraints) { + return ResponsiveWidget( + responsiveUtils: controller.responsiveUtils, + mobile: Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const LanguageTitleWidget(), + const SizedBox(height: 8), + Obx(() => LanguageMenuPopupDialogWidget( + languageSelected: controller.languageSelected.value, + maxWidth: constraints.maxWidth, + onSelectLanguageAction: controller.selectLanguage + )) + ] + ) + ), + desktop: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const LanguageTitleWidget(), + const SizedBox(height: 8), + SizedBox( + width: constraints.maxWidth / 2, + child: Obx(() => LanguageMenuPopupDialogWidget( + languageSelected: controller.languageSelected.value, + maxWidth: constraints.maxWidth / 2, + onSelectLanguageAction: controller.selectLanguage + )) + ), + ] + ) + ); + }) + ) ] ), ), diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart b/lib/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart deleted file mode 100644 index 3f29c3abc5..0000000000 --- a/lib/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart +++ /dev/null @@ -1,131 +0,0 @@ - -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/utils/style_utils.dart'; -import 'package:core/presentation/views/responsive/responsive_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/extensions/locale_extension.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -class ChangeLanguageButtonWidget extends StatelessWidget { - - final _controller = Get.find(); - final _responsiveUtils = Get.find(); - final _imagePaths = Get.find(); - - ChangeLanguageButtonWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - return ResponsiveWidget( - responsiveUtils: _responsiveUtils, - mobile: Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitleLanguageWidget(context), - const SizedBox(height: 8), - _buildLanguageMenu(context, constraints.maxWidth) - ] - ) - ), - desktop: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitleLanguageWidget(context), - const SizedBox(height: 8), - SizedBox( - width: constraints.maxWidth / 2, - child: _buildLanguageMenu(context, constraints.maxWidth / 2) - ), - ] - ) - ); - }); - } - - Widget _buildTitleLanguageWidget(BuildContext context) { - return Text( - AppLocalizations.of(context).language, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail - ) - ); - } - - Widget _buildLanguageMenu(BuildContext context, double maxWidth) { - return Obx(() => PortalTarget( - visible: _controller.isLanguageMenuOverlayOpen.isTrue, - portalFollower: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => _controller.toggleLanguageMenuOverlay() - ), - child: PortalTarget( - anchor: const Aligned( - follower: Alignment.topRight, - target: Alignment.bottomRight, - widthFactor: 1, - backup: Aligned( - follower: Alignment.topRight, - target: Alignment.bottomRight, - widthFactor: 1, - ), - ), - portalFollower: Obx(() => LanguageRegionOverlay( - listSupportedLanguages: _controller.listSupportedLanguages, - localeSelected: _controller.languageSelected.value, - maxWidth: maxWidth, - onSelectLanguageAction: _controller.selectLanguage, - )), - visible: _controller.isLanguageMenuOverlayOpen.isTrue, - child: _buildDropDownMenuButton(context, maxWidth) - ) - )); - } - - Widget _buildDropDownMenuButton(BuildContext context, double maxWidth) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () => _controller.toggleLanguageMenuOverlay(), - borderRadius: const BorderRadius.all(Radius.circular(10)), - child: Container( - height: 44, - width: maxWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: AppColor.colorInputBorderCreateMailbox, - width: 0.5, - ), - color: AppColor.colorItemSelected, - ), - padding: const EdgeInsetsDirectional.only(start: 12, end: 10), - child: Row(children: [ - Expanded(child: Text( - _controller.languageSelected.value.getLanguageNameByCurrentLocale(context), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - SvgPicture.asset(_imagePaths.icDropDown) - ]), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart index ae01960779..82eadadf4d 100644 --- a/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart +++ b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart @@ -29,10 +29,10 @@ class LanguageRegionOverlay extends StatelessWidget { width: maxWidth, margin: const EdgeInsets.only(top: 4, bottom: 24), padding: const EdgeInsets.all(8), - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ + borderRadius: BorderRadius.all(Radius.circular(12)), + boxShadow: [ BoxShadow( color: AppColor.colorShadowBgContentEmail, blurRadius: 24, diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart new file mode 100644 index 0000000000..6a55219bf0 --- /dev/null +++ b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart @@ -0,0 +1,88 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/extensions/locale_extension.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/lanuage_item_widget.dart'; +import 'package:tmail_ui_user/main/localizations/localization_service.dart'; + +class LanguageMenuPopupDialogWidget extends StatefulWidget { + + final Locale languageSelected; + final OnSelectLanguageAction onSelectLanguageAction; + final double maxWidth; + + const LanguageMenuPopupDialogWidget({ + super.key, + required this.languageSelected, + required this.maxWidth, + required this.onSelectLanguageAction, + }); + + @override + State createState() => _LanguageMenuPopupDialogWidgetState(); +} + +class _LanguageMenuPopupDialogWidgetState extends State { + + final ImagePaths _imagePaths = Get.find(); + + bool _visible = false; + + @override + Widget build(BuildContext context) { + return PortalTarget( + visible: _visible, + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, + onTap: () => setState(() => _visible = false) + ), + child: PortalTarget( + anchor: const Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + backup: Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + ), + ), + portalFollower: LanguageRegionOverlay( + listSupportedLanguages: LocalizationService.supportedLocales, + localeSelected: widget.languageSelected, + maxWidth: widget.maxWidth, + onSelectLanguageAction: (locale) { + setState(() => _visible = false); + widget.onSelectLanguageAction(locale); + }, + ), + visible: _visible, + child: TMailButtonWidget( + text: widget.languageSelected.getLanguageNameByCurrentLocale(context), + icon: _imagePaths.icDropDown, + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + maxWidth: widget.maxWidth, + iconAlignment: TextDirection.rtl, + borderRadius: 10, + expandedText: true, + backgroundColor: AppColor.colorItemSelected, + padding: const EdgeInsetsDirectional.all(10), + border: Border.all( + color: AppColor.colorInputBorderCreateMailbox, + width: 0.5, + ), + onTapActionCallback: () => setState(() => _visible = true) + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart b/lib/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart new file mode 100644 index 0000000000..6d79b91b1a --- /dev/null +++ b/lib/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart @@ -0,0 +1,20 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class LanguageTitleWidget extends StatelessWidget { + const LanguageTitleWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Text( + AppLocalizations.of(context).language, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: AppColor.colorContentEmail + ) + ); + } +} diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart index bfe29c01ab..11374852a2 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:model/model.dart'; @@ -31,7 +32,9 @@ class ManageAccountDashBoardView extends GetWidget FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isMobile + ? FocusScope.of(context).unfocus + : null, child: ResponsiveWidget( responsiveUtils: controller.responsiveUtils, desktop: Column(children: [ 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 9d8f66de44..475fc185a8 100644 --- a/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart +++ b/lib/features/manage_account/presentation/profiles/identities/identities_controller.dart @@ -251,6 +251,7 @@ class IdentitiesController extends ReloadableController implements BeforeReconne imagePaths: imagePaths, onDeleteIdentityAction: () => _dereferencePublicAssets(identity), ), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } @@ -329,6 +330,7 @@ class IdentitiesController extends ReloadableController implements BeforeReconne ..onCloseButtonAction(() => popBack()) ..onConfirmButtonAction('${AppLocalizations.of(currentContext!).got_it}!', () => popBack())) .build(), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/manage_account/presentation/vacation/vacation_controller.dart b/lib/features/manage_account/presentation/vacation/vacation_controller.dart index 80b9ce490c..21d176ce40 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_controller.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_controller.dart @@ -222,6 +222,7 @@ class VacationController extends BaseController { final timePicked = await showTimePicker( context: context, initialTime: currentTime ?? TimeOfDay.now(), + barrierDismissible: false, builder: (context, child) { return PointerInterceptor( child: Theme( diff --git a/lib/features/manage_account/presentation/vacation/vacation_view.dart b/lib/features/manage_account/presentation/vacation/vacation_view.dart index a27451ef6d..32d6b17890 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_view.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_view.dart @@ -278,7 +278,6 @@ class VacationView extends GetWidget with RichTextButtonMixi return SettingDetailViewBuilder( responsiveUtils: controller.responsiveUtils, padding: const EdgeInsets.symmetric(horizontal: 4), - onTapGestureDetector: () => controller.clearFocusEditor(context), child: vacationInputForm, ); } else { diff --git a/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart b/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart index 5343988029..19ff3e9f13 100644 --- a/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart @@ -259,6 +259,8 @@ class RulesFilterCreatorController extends BaseMailboxController { } void _setUpRuleFilterActions() { + if (_currentTMailRule == null) return; + if (_currentTMailRule!.action.appendIn.mailboxIds.isNotEmpty != true) return; final mailboxNode = findMailboxNodeById( diff --git a/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart b/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart index b15c790bcb..3896ccf9e3 100644 --- a/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart @@ -87,6 +87,7 @@ class RuleFilterCreatorView extends GetWidget { backgroundColor: Colors.black.withAlpha(24), body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), + excludeFromSemantics: true, child: Center(child: Card( color: Colors.transparent, shape: const RoundedRectangleBorder( @@ -143,30 +144,16 @@ class RuleFilterCreatorView extends GetWidget { )), const SizedBox(height: 24), _buildListRuleFilterConditionList(context, RuleFilterConditionScreenType.desktop), - Container( - padding: const EdgeInsets.only(top: 8), - child: InkWell( - onTap: controller.tapAddCondition, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - controller.imagePaths.icAddNewFolder, - fit: BoxFit.fill, - ), - const SizedBox(width: 15,), - Text( - AppLocalizations.of(context).addCondition, - maxLines: 1, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 17, - color: AppColor.primaryColor - ) - ) - ], - ), - ), + TMailButtonWidget( + icon: controller.imagePaths.icAddNewFolder, + text: AppLocalizations.of(context).addCondition, + maxLines: RuleFilterActionStyles.maxLines, + iconSpace: 15, + textStyle: RuleFilterActionStyles.buttonTextStyle, + margin: const EdgeInsets.only(top: 8), + backgroundColor: Colors.transparent, + mainAxisSize: MainAxisSize.min, + onTapActionCallback: controller.tapAddCondition, ), const SizedBox(height: 24), Text(AppLocalizations.of(context).actionTitleRulesFilter, @@ -200,26 +187,16 @@ class RuleFilterCreatorView extends GetWidget { }), Obx(() { if (controller.isShowAddAction.value == true) { - return Container( - padding: const EdgeInsets.only(top: 8), - child: InkWell( - onTap: controller.tapAddAction, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - controller.imagePaths.icAddNewFolder, - fit: BoxFit.fill, - ), - const SizedBox(width: 15,), - Text( - AppLocalizations.of(context).addAction, - maxLines: RuleFilterActionStyles.maxLines, - style: RuleFilterActionStyles.addActionButtonTextStyle - ) - ], - ), - ), + return TMailButtonWidget( + icon: controller.imagePaths.icAddNewFolder, + text: AppLocalizations.of(context).addAction, + maxLines: RuleFilterActionStyles.maxLines, + iconSpace: 15, + textStyle: RuleFilterActionStyles.buttonTextStyle, + margin: const EdgeInsets.only(top: 8), + backgroundColor: Colors.transparent, + mainAxisSize: MainAxisSize.min, + onTapActionCallback: controller.tapAddAction, ); } else { return const SizedBox.shrink(); @@ -304,30 +281,16 @@ class RuleFilterCreatorView extends GetWidget { )), const SizedBox(height: 24), _buildListRuleFilterConditionList(context, RuleFilterConditionScreenType.tablet), - Container( - padding: const EdgeInsets.only(top: 8), - child: InkWell( - onTap: controller.tapAddCondition, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - controller.imagePaths.icAddNewFolder, - fit: BoxFit.fill, - ), - const SizedBox(width: 15,), - Text( - AppLocalizations.of(context).addCondition, - maxLines: 1, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 17, - color: AppColor.primaryColor - ) - ) - ], - ), - ), + TMailButtonWidget( + icon: controller.imagePaths.icAddNewFolder, + text: AppLocalizations.of(context).addCondition, + maxLines: RuleFilterActionStyles.maxLines, + iconSpace: 15, + textStyle: RuleFilterActionStyles.buttonTextStyle, + margin: const EdgeInsets.only(top: 8), + backgroundColor: Colors.transparent, + mainAxisSize: MainAxisSize.min, + onTapActionCallback: controller.tapAddCondition, ), const SizedBox(height: 24), Text(AppLocalizations.of(context).actionTitleRulesFilter, @@ -361,26 +324,16 @@ class RuleFilterCreatorView extends GetWidget { }), Obx(() { if (controller.isShowAddAction.value == true) { - return Container( - padding: const EdgeInsets.only(top: 8), - child: InkWell( - onTap: controller.tapAddAction, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - controller.imagePaths.icAddNewFolder, - fit: BoxFit.fill, - ), - const SizedBox(width: 15,), - Text( - AppLocalizations.of(context).addAction, - maxLines: RuleFilterActionStyles.maxLines, - style: RuleFilterActionStyles.addActionButtonTextStyle - ) - ], - ), - ), + return TMailButtonWidget( + icon: controller.imagePaths.icAddNewFolder, + text: AppLocalizations.of(context).addAction, + maxLines: RuleFilterActionStyles.maxLines, + iconSpace: 15, + textStyle: RuleFilterActionStyles.buttonTextStyle, + margin: const EdgeInsets.only(top: 8), + backgroundColor: Colors.transparent, + mainAxisSize: MainAxisSize.min, + onTapActionCallback: controller.tapAddAction, ); } else { return const SizedBox.shrink(); @@ -448,7 +401,6 @@ class RuleFilterCreatorView extends GetWidget { child: Padding( padding: const EdgeInsets.all(24.0), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ Obx(() => RulesFilterInputField( hintText: AppLocalizations.of(context).rulesNameHintTextInput, @@ -475,44 +427,35 @@ class RuleFilterCreatorView extends GetWidget { )), const SizedBox(height: 24), _buildListRuleFilterConditionList(context, RuleFilterConditionScreenType.mobile), - Container( - padding: const EdgeInsets.only(top: 12), - child: InkWell( - onTap: controller.tapAddCondition, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - controller.imagePaths.icAddNewFolder, - fit: BoxFit.fill, - ), - const SizedBox(width: 15,), - Text( - AppLocalizations.of(context).addCondition, - maxLines: 1, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 17, - color: AppColor.primaryColor - ) - ) - ], - ), - ), + TMailButtonWidget( + icon: controller.imagePaths.icAddNewFolder, + text: AppLocalizations.of(context).addCondition, + maxLines: RuleFilterActionStyles.maxLines, + iconSpace: 15, + textStyle: RuleFilterActionStyles.buttonTextStyle, + margin: const EdgeInsets.only(top: 8), + backgroundColor: Colors.transparent, + mainAxisSize: MainAxisSize.min, + onTapActionCallback: controller.tapAddCondition, ), const Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Divider(), ), - Text(AppLocalizations.of(context).actionTitleRulesFilter, + Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + AppLocalizations.of(context).actionTitleRulesFilter, overflow: CommonTextStyle.defaultTextOverFlow, softWrap: CommonTextStyle.defaultSoftWrap, maxLines: 1, style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black)), + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black, + ) + ), + ), const SizedBox(height: 24), Obx(() { return RuleFilterActionListWidget( @@ -544,27 +487,16 @@ class RuleFilterCreatorView extends GetWidget { }), Obx(() { if (controller.isShowAddAction.value == true) { - return Container( - padding: const EdgeInsets.only(top: 12), - child: InkWell( - onTap: controller.tapAddAction, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - controller.imagePaths.icAddNewFolder, - fit: BoxFit.fill, - ), - const SizedBox(width: 15,), - Text( - AppLocalizations.of(context).addAction, - maxLines: RuleFilterActionStyles.maxLines, - style: RuleFilterActionStyles.addActionButtonTextStyle - ) - ], - ), - ), + return TMailButtonWidget( + icon: controller.imagePaths.icAddNewFolder, + text: AppLocalizations.of(context).addAction, + maxLines: RuleFilterActionStyles.maxLines, + iconSpace: 15, + textStyle: RuleFilterActionStyles.buttonTextStyle, + margin: const EdgeInsets.only(top: 8), + backgroundColor: Colors.transparent, + mainAxisSize: MainAxisSize.min, + onTapActionCallback: controller.tapAddAction, ); } else { return const SizedBox.shrink(); diff --git a/lib/features/rules_filter_creator/presentation/styles/rule_filter_action_styles.dart b/lib/features/rules_filter_creator/presentation/styles/rule_filter_action_styles.dart index 8d37fd9cef..91ead374be 100644 --- a/lib/features/rules_filter_creator/presentation/styles/rule_filter_action_styles.dart +++ b/lib/features/rules_filter_creator/presentation/styles/rule_filter_action_styles.dart @@ -15,7 +15,7 @@ class RuleFilterActionStyles { color: color, ); static const double itemDistance = 12.0; - static const TextStyle addActionButtonTextStyle = TextStyle( + static const TextStyle buttonTextStyle = TextStyle( fontWeight: FontWeight.w500, fontSize: 17.0, color: AppColor.primaryColor, diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index 15d72ce66d..a30c1cfb99 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -54,7 +54,9 @@ class SearchEmailView extends GetWidget return Scaffold( body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isWeb + ? null + : FocusScope.of(context).unfocus, child: Container( color: Colors.white, child: Column(children: [ @@ -386,6 +388,7 @@ class SearchEmailView extends GetWidget ) { return EmailReceiveTimeType.values .map((timeType) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: EmailReceiveTimeActionTileWidget( receiveTimeSelected: receiveTimeSelected, @@ -428,6 +431,7 @@ class SearchEmailView extends GetWidget ) { return EmailSortOrderType.values .map((sortType) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: EmailSortByActionTitleWidget( sortType: sortType, @@ -772,6 +776,7 @@ class SearchEmailView extends GetWidget List _popupMenuActionTile(BuildContext context, PresentationEmail email) { return [ PopupMenuItem( + enabled: false, padding: const EdgeInsets.symmetric(horizontal: 8), child: _markAsEmailSpamOrUnSpamAction(context, email)), ]; diff --git a/lib/features/search/mailbox/presentation/search_mailbox_view.dart b/lib/features/search/mailbox/presentation/search_mailbox_view.dart index beeb620712..0dd27f8e22 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_view.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_view.dart @@ -34,32 +34,34 @@ class SearchMailboxView extends GetWidget @override Widget build(BuildContext context) { return Scaffold( - body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: PlatformInfo.isWeb - ? PointerInterceptor(child: _buildSearchBody(context)) - : SafeArea(child: _buildSearchBody(context)), - ), + body: PlatformInfo.isWeb + ? PointerInterceptor(child: _buildSearchBody(context)) + : GestureDetector( + onTap: FocusScope.of(context).unfocus, + child: SafeArea(child: _buildSearchBody(context))), ); } Widget _buildSearchBody(BuildContext context) { - return Container( - color: backgroundColor ?? Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Column(children: [ - Container( - color: Colors.transparent, - padding: SearchMailboxUtils.getPaddingAppBar(context, controller.responsiveUtils), - child: _buildSearchInputForm(context) - ), - if (!controller.responsiveUtils.isWebDesktop(context)) - const Divider(color: AppColor.colorDividerComposer, height: 1), - _buildLoadingView(), - Expanded( - child: _buildMailboxListView(context) - ) - ]), + return Semantics( + container: true, + child: Container( + color: backgroundColor ?? Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column(children: [ + Container( + color: Colors.transparent, + padding: SearchMailboxUtils.getPaddingAppBar(context, controller.responsiveUtils), + child: _buildSearchInputForm(context) + ), + if (!controller.responsiveUtils.isWebDesktop(context)) + const Divider(color: AppColor.colorDividerComposer, height: 1), + _buildLoadingView(), + Expanded( + child: _buildMailboxListView(context) + ) + ]), + ), ); } diff --git a/lib/features/thread/presentation/mixin/base_email_item_tile.dart b/lib/features/thread/presentation/mixin/base_email_item_tile.dart index 51b539e28d..d75fc70636 100644 --- a/lib/features/thread/presentation/mixin/base_email_item_tile.dart +++ b/lib/features/thread/presentation/mixin/base_email_item_tile.dart @@ -17,7 +17,6 @@ import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; typedef OnPressEmailActionClick = void Function(EmailActionType, PresentationEmail); typedef OnMoreActionClick = void Function(PresentationEmail, RelativeRect?); @@ -321,18 +320,6 @@ mixin BaseEmailItemTile { fit: BoxFit.fill); } - String? messageToolTipForAnsweredOrForwarded(BuildContext context, PresentationEmail presentationEmail) { - if (presentationEmail.isAnsweredAndForwarded) { - return AppLocalizations.of(context).repliedAndForwardedMessage; - } else if (presentationEmail.isAnswered) { - return AppLocalizations.of(context).repliedMessage; - } else if (presentationEmail.isForwarded){ - return AppLocalizations.of(context).forwardedMessage; - } else { - return null; - } - } - Widget buildCalendarEventIcon({ required BuildContext context, required PresentationEmail presentationEmail diff --git a/lib/features/thread/presentation/mixin/email_action_controller.dart b/lib/features/thread/presentation/mixin/email_action_controller.dart index d6899667b6..556031b47e 100644 --- a/lib/features/thread/presentation/mixin/email_action_controller.dart +++ b/lib/features/thread/presentation/mixin/email_action_controller.dart @@ -214,6 +214,7 @@ mixin EmailActionController { AppLocalizations.of(context).cancel, () => popBack())) .build()), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index d58b75886a..c954be679a 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -804,10 +804,6 @@ class ThreadController extends BaseController with EmailActionController { bool get isSearchActive => searchController.isSearchEmailRunning; - void clearTextSearch() { - searchController.clearTextSearch(); - } - void _searchEmail({UnsignedInt? limit, bool needRefreshSearchState = false}) { if (_session != null && _accountId != null) { if (!needRefreshSearchState && listEmailController.hasClients) { diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 3f6b52b9ba..5da40c8f97 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -793,6 +793,7 @@ class ThreadView extends GetWidget PresentationMailbox? mailboxContain ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: popupItem( mailboxContain?.isSpam == true ? controller.imagePaths.icNotSpam : controller.imagePaths.icSpam, @@ -820,6 +821,7 @@ class ThreadView extends GetWidget PresentationMailbox? mailboxContain ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: popupItem( controller.imagePaths.icOpenInNewTab, @@ -843,6 +845,7 @@ class ThreadView extends GetWidget PresentationEmail email ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: popupItem( controller.imagePaths.icMailboxArchived, diff --git a/lib/features/thread/presentation/widgets/answered_forwarded_icon.dart b/lib/features/thread/presentation/widgets/answered_forwarded_icon.dart new file mode 100644 index 0000000000..e61ccac08f --- /dev/null +++ b/lib/features/thread/presentation/widgets/answered_forwarded_icon.dart @@ -0,0 +1,76 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class AnsweredForwardedIcon extends StatelessWidget { + + final bool isAnswered; + final bool isForwarded; + final ImagePaths imagePaths; + + const AnsweredForwardedIcon({ + super.key, + required this.isAnswered, + required this.isForwarded, + required this.imagePaths + }); + + @override + Widget build(BuildContext context) { + final iconString = _getIcon(); + if (iconString.isEmpty) { + return const SizedBox.shrink(); + } + + final messageToolTip = _getMessageToolTip(context); + final iconWidget = Padding( + padding: const EdgeInsets.all(2), + child: SvgPicture.asset( + iconString, + width: 20, + height: 20, + colorFilter: AppColor.colorAttachmentIcon.asFilter(), + fit: BoxFit.fill, + ), + ); + + if (messageToolTip.isEmpty) { + return iconWidget; + } else { + return MouseRegion( + cursor: SystemMouseCursors.basic, + child: Tooltip( + message: messageToolTip, + child: iconWidget + ), + ); + } + } + + String _getIcon() { + if (isAnswered && isForwarded) { + return imagePaths.icReplyAndForward; + } else if (isAnswered) { + return imagePaths.icReply; + } else if (isForwarded) { + return imagePaths.icForwarded; + } else { + return ''; + } + } + + String _getMessageToolTip(BuildContext context) { + if (isAnswered && isForwarded) { + return AppLocalizations.of(context).repliedAndForwardedMessage; + } else if (isAnswered) { + return AppLocalizations.of(context).repliedMessage; + } else if (isForwarded){ + return AppLocalizations.of(context).forwardedMessage; + } else { + return ''; + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart index abb8241a4a..4e0390d125 100644 --- a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/tap_down_details_extension.dart'; import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -12,6 +13,7 @@ import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/presentation/mixin/base_email_item_tile.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/answered_forwarded_icon.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class EmailTileBuilder extends StatefulWidget { @@ -330,6 +332,7 @@ class _EmailTileBuilderState extends State with BaseEmailItem EmailActionType.preview, widget.presentationEmail ), + excludeFromSemantics: true, onHover: (value) => _hoverNotifier.value = value, hoverColor: AppColor.colorEmailTileHoverWeb, borderRadius: const BorderRadius.all(Radius.circular(14)), @@ -339,93 +342,68 @@ class _EmailTileBuilderState extends State with BaseEmailItem alignment: Alignment.center, child: Row(children: [ const SizedBox(width: 10), - buildIconWeb( - icon: ValueListenableBuilder( - valueListenable: _hoverNotifier, - builder: (context, isHovered, child) { - return SvgPicture.asset( - widget.presentationEmail.isSelected - ? imagePaths.icCheckboxSelected - : imagePaths.icCheckboxUnselected, - colorFilter: ColorFilter.mode( - isHovered || widget.presentationEmail.isSelected - ? AppColor.primaryColor - : AppColor.colorEmailTileCheckboxUnhover, - BlendMode.srcIn), - width: 20, - height: 20); - }, - ), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - tooltip: widget.presentationEmail.isSelected - ? AppLocalizations.of(context).selected - : AppLocalizations.of(context).notSelected, - onTap: () { - widget.emailActionClick?.call( - EmailActionType.selection, - widget.presentationEmail + ValueListenableBuilder( + valueListenable: _hoverNotifier, + builder: (context, isHovered, _) { + return TMailButtonWidget.fromIcon( + icon: widget.presentationEmail.isSelected + ? imagePaths.icCheckboxSelected + : imagePaths.icCheckboxUnselected, + iconSize: 20, + iconColor: isHovered || widget.presentationEmail.isSelected + ? AppColor.primaryColor + : AppColor.colorEmailTileCheckboxUnhover, + padding: const EdgeInsets.all(5), + backgroundColor: Colors.transparent, + tooltipMessage: widget.presentationEmail.isSelected + ? AppLocalizations.of(context).selected + : AppLocalizations.of(context).notSelected, + onTapActionCallback: () => widget.emailActionClick?.call( + EmailActionType.selection, + widget.presentationEmail + ), ); }, ), - buildIconWeb( - icon: SvgPicture.asset( - widget.presentationEmail.hasStarred - ? imagePaths.icStar - : imagePaths.icUnStar, - width: 20, - height: 20, - fit: BoxFit.fill - ), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - tooltip: widget.presentationEmail.hasStarred + TMailButtonWidget.fromIcon( + icon: widget.presentationEmail.hasStarred + ? imagePaths.icStar + : imagePaths.icUnStar, + iconSize: 20, + padding: const EdgeInsets.all(5), + backgroundColor: Colors.transparent, + tooltipMessage: widget.presentationEmail.hasStarred ? AppLocalizations.of(context).starred : AppLocalizations.of(context).not_starred, - onTap: () => widget.emailActionClick?.call( + onTapActionCallback: () => widget.emailActionClick?.call( widget.presentationEmail.hasStarred ? EmailActionType.unMarkAsStarred : EmailActionType.markAsStarred, widget.presentationEmail - ) - ), - buildIconWeb( - icon: buildIconAnsweredOrForwarded(presentationEmail: widget.presentationEmail), - tooltip: messageToolTipForAnsweredOrForwarded(context, widget.presentationEmail), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - splashRadius: 1 + ), ), - buildIconWeb( - icon: widget.presentationEmail.hasRead - ? const SizedBox(width: 20, height: 20) - : Container( - alignment: Alignment.center, - width: 20, - height: 20, - child: SvgPicture.asset( - imagePaths.icUnreadStatus, - width: 9, - height: 9, - fit: BoxFit.fill - ), - ), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - tooltip: widget.presentationEmail.hasRead - ? null - : AppLocalizations.of(context).mark_as_read, - onTap: widget.presentationEmail.hasRead ? null : () { - widget.emailActionClick?.call( + if (widget.presentationEmail.isAnsweredOrForwarded) + AnsweredForwardedIcon( + isAnswered: widget.presentationEmail.isAnswered, + isForwarded: widget.presentationEmail.isForwarded, + imagePaths: imagePaths, + ) + else + const SizedBox(width: 24, height: 24), + if (widget.presentationEmail.hasRead) + const SizedBox(width: 28, height: 28) + else + TMailButtonWidget.fromIcon( + icon: imagePaths.icUnreadStatus, + iconSize: 9, + padding: const EdgeInsets.all(10), + backgroundColor: Colors.transparent, + tooltipMessage: AppLocalizations.of(context).mark_as_read, + onTapActionCallback: () => widget.emailActionClick?.call( EmailActionType.markAsRead, widget.presentationEmail - ); - }, - ), + ), + ), buildIconAvatarText( widget.presentationEmail, iconSize: 32, @@ -469,7 +447,7 @@ class _EmailTileBuilderState extends State with BaseEmailItem EdgeInsetsGeometry _getPaddingItem(BuildContext context) { if (responsiveUtils.isDesktop(context)) { - return const EdgeInsets.symmetric(vertical: 4); + return const EdgeInsets.symmetric(vertical: 8); } else if (responsiveUtils.isTablet(context)) { return const EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 24); } else { diff --git a/lib/main.dart b/lib/main.dart index 86435990a5..611de84ab1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/utils/theme_utils.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/build_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; @@ -18,6 +19,7 @@ import 'package:worker_manager/worker_manager.dart'; void main() async { initLogger(() async { WidgetsFlutterBinding.ensureInitialized(); + SemanticsBinding.instance.ensureSemantics(); ThemeUtils.setSystemLightUIStyle(); await Future.wait([ diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index 5622a6de6f..2cca6a9252 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -124,6 +124,8 @@ class PresentationEmail with EquatableMixin { bool get isAnsweredAndForwarded => isAnswered && isForwarded; + bool get isAnsweredOrForwarded => isAnswered || isForwarded; + bool get withAttachments => hasAttachment == true; bool get isSelected => selectMode == SelectMode.ACTIVE; diff --git a/pubspec.lock b/pubspec.lock index 69a0d59200..d1c470be3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1146,8 +1146,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: "3274d7fe8c711e7466890b6a3948e9708877ca4a" + ref: "bugfix/avoid-called-twice-when-editor-loses-focus" + resolved-ref: "60c4b31714b03a66c4deb741a7c580d97d9fe3ce" url: "https://github.com/linagora/html-editor-enhanced.git" source: git version: "3.1.1" diff --git a/pubspec.yaml b/pubspec.yaml index fa81fcad4d..a7fe050001 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,7 +60,7 @@ dependencies: html_editor_enhanced: git: url: https://github.com/linagora/html-editor-enhanced.git - ref: main + ref: bugfix/avoid-called-twice-when-editor-loses-focus jmap_dart_client: git: