TF-2666 Edit one recipient in To/Cc/Bcc on mobile
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -7,17 +7,20 @@ import 'package:universal_html/html.dart' as html;
|
||||
|
||||
typedef OnRightMouseClickAction = void Function(RelativeRect position);
|
||||
typedef OnDoubleClickAction = void Function(RelativeRect position);
|
||||
typedef OnLongPressAction = void Function();
|
||||
|
||||
class SmartInteractionWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final OnRightMouseClickAction onRightMouseClickAction;
|
||||
final OnDoubleClickAction onDoubleClickAction;
|
||||
final OnLongPressAction onLongPressAction;
|
||||
|
||||
const SmartInteractionWidget({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.onRightMouseClickAction,
|
||||
required this.onDoubleClickAction,
|
||||
required this.onLongPressAction,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -25,8 +28,8 @@ class SmartInteractionWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
|
||||
final GlobalKey _childKey = GlobalKey();
|
||||
|
||||
GlobalKey? _childKey;
|
||||
Offset? _lastPointerEventOffset;
|
||||
StreamSubscription<html.MouseEvent>? _contextMenuSubscription;
|
||||
|
||||
@@ -34,6 +37,7 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (PlatformInfo.isWeb) {
|
||||
_childKey = GlobalKey();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_addWebContextMenuListener();
|
||||
});
|
||||
@@ -47,7 +51,7 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
|
||||
|
||||
void _onContextMenuListener(html.MouseEvent event) {
|
||||
final renderBox =
|
||||
_childKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
_childKey?.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) return;
|
||||
|
||||
final localPosition = Offset(
|
||||
@@ -79,7 +83,7 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
|
||||
Offset offset,
|
||||
) {
|
||||
final widgetBox =
|
||||
_childKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
_childKey?.currentContext?.findRenderObject() as RenderBox?;
|
||||
final overlayBox =
|
||||
Overlay.maybeOf(context)?.context.findRenderObject() as RenderBox?;
|
||||
|
||||
@@ -118,6 +122,8 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleLongPress() => widget.onLongPressAction();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
@@ -135,14 +141,17 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return widget.child;
|
||||
return GestureDetector(
|
||||
onLongPress: _handleLongPress,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_lastPointerEventOffset = null;
|
||||
if (PlatformInfo.isWeb) {
|
||||
_lastPointerEventOffset = null;
|
||||
_contextMenuSubscription?.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
|
||||
@@ -1497,28 +1497,36 @@ class ComposerController extends BaseController
|
||||
} else {
|
||||
switch(prefixEmailAddress) {
|
||||
case PrefixEmailAddress.to:
|
||||
toAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
if (mailboxDashBoardController.isPopupMenuOpened.isFalse) {
|
||||
toAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
final inputToEmail = toEmailAddressController.text;
|
||||
if (inputToEmail.trim().isNotEmpty) {
|
||||
autoCreateEmailTagForType(PrefixEmailAddress.to, inputToEmail);
|
||||
}
|
||||
break;
|
||||
case PrefixEmailAddress.cc:
|
||||
ccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
if (mailboxDashBoardController.isPopupMenuOpened.isFalse) {
|
||||
ccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
final inputCcEmail = ccEmailAddressController.text;
|
||||
if (inputCcEmail.trim().isNotEmpty) {
|
||||
autoCreateEmailTagForType(PrefixEmailAddress.cc, inputCcEmail);
|
||||
}
|
||||
break;
|
||||
case PrefixEmailAddress.bcc:
|
||||
bccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
if (mailboxDashBoardController.isPopupMenuOpened.isFalse) {
|
||||
bccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
final inputBccEmail = bccEmailAddressController.text;
|
||||
if (inputBccEmail.trim().isNotEmpty) {
|
||||
autoCreateEmailTagForType(PrefixEmailAddress.bcc, inputBccEmail);
|
||||
}
|
||||
break;
|
||||
case PrefixEmailAddress.replyTo:
|
||||
replyToAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
if (mailboxDashBoardController.isPopupMenuOpened.isFalse) {
|
||||
replyToAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
final inputReplyToEmail = replyToEmailAddressController.text;
|
||||
if (inputReplyToEmail.trim().isNotEmpty) {
|
||||
autoCreateEmailTagForType(PrefixEmailAddress.replyTo, inputReplyToEmail);
|
||||
@@ -1783,6 +1791,9 @@ class ComposerController extends BaseController
|
||||
if (menuMoreOptionController?.menuIsShowing == true) {
|
||||
menuMoreOptionController?.hideMenu();
|
||||
}
|
||||
if (mailboxDashBoardController.isPopupMenuOpened.isTrue) {
|
||||
popBack();
|
||||
}
|
||||
}
|
||||
|
||||
void handleOnMouseDownHtmlEditorWeb() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:get/get.dart';
|
||||
import 'package:model/email/prefix_email_address.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_content_height_exceeded_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_edit_recipient_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_open_context_menu_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/mark_as_important_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
|
||||
@@ -140,6 +141,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEnableAllRecipientsInputAction: controller.handleEnableRecipientsInputAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
)),
|
||||
Obx(() {
|
||||
if (controller.ccRecipientState.value == PrefixRecipientState.enabled) {
|
||||
@@ -163,6 +165,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
@@ -190,6 +193,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
@@ -217,6 +221,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEnableAllRecipientsInputAction: controller.handleEnableRecipientsInputAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
@@ -340,6 +345,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEnableAllRecipientsInputAction: controller.handleEnableRecipientsInputAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
),
|
||||
if (controller.ccRecipientState.value == PrefixRecipientState.enabled)
|
||||
RecipientComposerWidget(
|
||||
@@ -362,6 +368,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
),
|
||||
if (controller.bccRecipientState.value == PrefixRecipientState.enabled)
|
||||
RecipientComposerWidget(
|
||||
@@ -384,6 +391,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
),
|
||||
if (controller.replyToRecipientState.value == PrefixRecipientState.enabled)
|
||||
RecipientComposerWidget(
|
||||
@@ -405,6 +413,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
|
||||
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
|
||||
onFocusNextAddressAction: controller.handleFocusNextAddressAction,
|
||||
onEditRecipientAction: controller.onEditRecipient,
|
||||
),
|
||||
],
|
||||
)),
|
||||
|
||||
+74
-23
@@ -1,14 +1,17 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:model/email/prefix_email_address.dart';
|
||||
import 'package:model/extensions/email_address_extension.dart';
|
||||
import 'package:model/mailbox/expand_mode.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/context_item_email_address_action_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/email_address_action_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/popup_menu_item_email_address_action_type.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
@@ -17,31 +20,63 @@ extension HandleEditRecipientExtension on ComposerController {
|
||||
BuildContext context,
|
||||
PrefixEmailAddress prefix,
|
||||
EmailAddress emailAddress,
|
||||
RelativeRect position,
|
||||
RelativeRect? position,
|
||||
) {
|
||||
final popupMenuItems = EmailAddressActionType.values.map((type) {
|
||||
return PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
child: PopupMenuItemActionWidget(
|
||||
menuAction: PopupMenuItemEmailAddressActionType(
|
||||
type,
|
||||
AppLocalizations.of(context),
|
||||
imagePaths,
|
||||
),
|
||||
menuActionClick: (menuAction) {
|
||||
popBack();
|
||||
_handleEmailAddressActionTypeClick(
|
||||
context,
|
||||
menuAction.action,
|
||||
prefix,
|
||||
emailAddress,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
if (position == null) {
|
||||
clearFocus(context);
|
||||
|
||||
openPopupMenuAction(context, position, popupMenuItems);
|
||||
final contextMenuActions = EmailAddressActionType.values
|
||||
.map((action) => ContextItemEmailAddressActionType(
|
||||
action,
|
||||
AppLocalizations.of(context),
|
||||
imagePaths
|
||||
))
|
||||
.toList();
|
||||
|
||||
openBottomSheetContextMenuAction(
|
||||
context: context,
|
||||
itemActions: contextMenuActions,
|
||||
onContextMenuActionClick: (menuAction) {
|
||||
popBack();
|
||||
_handleEmailAddressActionTypeClick(
|
||||
context,
|
||||
menuAction.action,
|
||||
prefix,
|
||||
emailAddress,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (mailboxDashBoardController.isPopupMenuOpened.isTrue) return;
|
||||
|
||||
final popupMenuItems = EmailAddressActionType.values.map((type) {
|
||||
return PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
child: PopupMenuItemActionWidget(
|
||||
menuAction: PopupMenuItemEmailAddressActionType(
|
||||
type,
|
||||
AppLocalizations.of(context),
|
||||
imagePaths,
|
||||
),
|
||||
menuActionClick: (menuAction) {
|
||||
popBack();
|
||||
_handleEmailAddressActionTypeClick(
|
||||
context,
|
||||
menuAction.action,
|
||||
prefix,
|
||||
emailAddress,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
mailboxDashBoardController.openPopupMenu(
|
||||
context,
|
||||
position,
|
||||
popupMenuItems,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleEmailAddressActionTypeClick(
|
||||
@@ -121,6 +156,22 @@ extension HandleEditRecipientExtension on ComposerController {
|
||||
);
|
||||
bccAddressFocusNode?.requestFocus();
|
||||
break;
|
||||
case PrefixEmailAddress.replyTo:
|
||||
listReplyToEmailAddress.remove(emailAddress);
|
||||
replyToAddressExpandMode.value = ExpandMode.EXPAND;
|
||||
replyToAddressExpandMode.refresh();
|
||||
|
||||
replyToEmailAddressController.text = emailAddress.emailAddress;
|
||||
replyToEmailAddressController.value = replyToEmailAddressController.value.copyWith(
|
||||
text: emailAddress.emailAddress,
|
||||
selection: TextSelection(
|
||||
baseOffset: emailAddress.emailAddress.length,
|
||||
extentOffset: emailAddress.emailAddress.length,
|
||||
),
|
||||
composing: TextRange.empty,
|
||||
);
|
||||
replyToAddressFocusNode?.requestFocus();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/context_menu/context_menu_item_action.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/email_address_action_type.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class ContextItemEmailAddressActionType
|
||||
extends ContextMenuItemActionRequiredIcon<EmailAddressActionType> {
|
||||
final AppLocalizations appLocalizations;
|
||||
final ImagePaths imagePaths;
|
||||
|
||||
ContextItemEmailAddressActionType(
|
||||
super.action,
|
||||
this.appLocalizations,
|
||||
this.imagePaths,
|
||||
);
|
||||
|
||||
@override
|
||||
String get actionIcon => action.getContextMenuIcon(imagePaths);
|
||||
|
||||
@override
|
||||
String get actionName => action.getContextMenuTitle(appLocalizations);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ typedef OnEditRecipientAction = void Function(
|
||||
BuildContext context,
|
||||
PrefixEmailAddress prefix,
|
||||
EmailAddress emailAddress,
|
||||
RelativeRect position,
|
||||
RelativeRect? position,
|
||||
);
|
||||
|
||||
class RecipientComposerWidget extends StatefulWidget {
|
||||
|
||||
@@ -56,40 +56,51 @@ class RecipientTagItemWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget tagWidget = Chip(
|
||||
labelPadding: EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: DirectionUtils.isDirectionRTLByHasAnyRtl(currentEmailAddress.asString()) ? 0 : 2
|
||||
Widget tagWidget = SmartInteractionWidget(
|
||||
onRightMouseClickAction: (position) => _onEditRecipientAction(
|
||||
context,
|
||||
position: position,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
label: Text(
|
||||
key: Key('label_recipient_tag_item_${prefix.name}_$index'),
|
||||
currentEmailAddress.asString(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
onDoubleClickAction: (position) => _onEditRecipientAction(
|
||||
context,
|
||||
position: position,
|
||||
),
|
||||
deleteIcon: SvgPicture.asset(
|
||||
imagePaths.icClose,
|
||||
key: Key('delete_icon_recipient_tag_item_${prefix.name}_$index'),
|
||||
fit: BoxFit.fill
|
||||
onLongPressAction: () => _onEditRecipientAction(context),
|
||||
child: Chip(
|
||||
labelPadding: EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: DirectionUtils.isDirectionRTLByHasAnyRtl(currentEmailAddress.asString()) ? 0 : 2
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
label: Text(
|
||||
key: Key('label_recipient_tag_item_${prefix.name}_$index'),
|
||||
currentEmailAddress.asString(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
),
|
||||
deleteIcon: SvgPicture.asset(
|
||||
imagePaths.icClose,
|
||||
key: Key('delete_icon_recipient_tag_item_${prefix.name}_$index'),
|
||||
fit: BoxFit.fill
|
||||
),
|
||||
labelStyle: RecipientTagItemWidgetStyle.labelTextStyle,
|
||||
backgroundColor: _getTagBackgroundColor(),
|
||||
side: _getTagBorderSide(),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(RecipientTagItemWidgetStyle.radius)),
|
||||
),
|
||||
avatar: currentEmailAddress.displayName.isNotEmpty
|
||||
? GradientCircleAvatarIcon(
|
||||
key: Key('avatar_icon_recipient_tag_item_${prefix.name}_$index'),
|
||||
colors: currentEmailAddress.avatarColors,
|
||||
label: currentEmailAddress.displayName.firstLetterToUpperCase,
|
||||
labelFontSize: RecipientTagItemWidgetStyle.avatarLabelFontSize,
|
||||
iconSize: RecipientTagItemWidgetStyle.avatarIconSize,
|
||||
)
|
||||
: null,
|
||||
onDeleted: () => onDeleteTagAction?.call(currentEmailAddress),
|
||||
),
|
||||
labelStyle: RecipientTagItemWidgetStyle.labelTextStyle,
|
||||
backgroundColor: _getTagBackgroundColor(),
|
||||
side: _getTagBorderSide(),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(RecipientTagItemWidgetStyle.radius)),
|
||||
),
|
||||
avatar: currentEmailAddress.displayName.isNotEmpty
|
||||
? GradientCircleAvatarIcon(
|
||||
key: Key('avatar_icon_recipient_tag_item_${prefix.name}_$index'),
|
||||
colors: currentEmailAddress.avatarColors,
|
||||
label: currentEmailAddress.displayName.firstLetterToUpperCase,
|
||||
labelFontSize: RecipientTagItemWidgetStyle.avatarLabelFontSize,
|
||||
iconSize: RecipientTagItemWidgetStyle.avatarIconSize,
|
||||
)
|
||||
: null,
|
||||
onDeleted: () => onDeleteTagAction?.call(currentEmailAddress),
|
||||
);
|
||||
|
||||
if (PlatformInfo.isWeb || isTestingForWeb) {
|
||||
@@ -103,17 +114,7 @@ class RecipientTagItemWidget extends StatelessWidget {
|
||||
childWhenDragging: DraggableRecipientTagWidget(emailAddress: currentEmailAddress),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.grab,
|
||||
child: SmartInteractionWidget(
|
||||
onRightMouseClickAction: (position) => _onEditRecipientAction(
|
||||
context,
|
||||
position,
|
||||
),
|
||||
onDoubleClickAction: (position) => _onEditRecipientAction(
|
||||
context,
|
||||
position,
|
||||
),
|
||||
child: tagWidget,
|
||||
),
|
||||
child: tagWidget,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -185,7 +186,7 @@ class RecipientTagItemWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
void _onEditRecipientAction(BuildContext context, RelativeRect position) {
|
||||
void _onEditRecipientAction(BuildContext context, {RelativeRect? position}) {
|
||||
onEditRecipientAction?.call(
|
||||
context,
|
||||
prefix,
|
||||
|
||||
Reference in New Issue
Block a user