diff --git a/core/lib/presentation/views/avatar/gradient_circle_avatar_icon.dart b/core/lib/presentation/views/avatar/gradient_circle_avatar_icon.dart index acc50716c..3873384d0 100644 --- a/core/lib/presentation/views/avatar/gradient_circle_avatar_icon.dart +++ b/core/lib/presentation/views/avatar/gradient_circle_avatar_icon.dart @@ -8,6 +8,7 @@ class GradientCircleAvatarIcon extends StatelessWidget { final double iconSize; final double labelFontSize; final String label; + final TextStyle? textStyle; const GradientCircleAvatarIcon({ Key? key, @@ -15,6 +16,7 @@ class GradientCircleAvatarIcon extends StatelessWidget { this.iconSize = 40, this.label = '', this.labelFontSize = 24.0, + this.textStyle, }) : super(key: key); @override @@ -33,13 +35,13 @@ class GradientCircleAvatarIcon extends StatelessWidget { ), color: AppColor.avatarColor ), - child: Text( - label, - style: TextStyle( + child: DefaultTextStyle( + style: textStyle ?? TextStyle( color: Colors.white, fontSize: labelFontSize, fontWeight: FontWeight.w600 - ) + ), + child: Text(label), ) ); } diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 6552006d0..5f35d058d 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -49,6 +49,7 @@ import 'package:tmail_ui_user/features/composer/presentation/controller/rich_tex import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; @@ -1867,4 +1868,28 @@ class ComposerController extends BaseController { final newContentHtml = contentHtml.removeEditorStartTag(); return newContentHtml; } + + void removeDraggableEmailAddress(DraggableEmailAddress draggableEmailAddress) { + log('ComposerController::removeDraggableEmailAddress: $draggableEmailAddress'); + switch(draggableEmailAddress.prefix) { + case PrefixEmailAddress.to: + listToEmailAddress.remove(draggableEmailAddress.emailAddress); + toAddressExpandMode.value = ExpandMode.EXPAND; + break; + case PrefixEmailAddress.cc: + listCcEmailAddress.remove(draggableEmailAddress.emailAddress); + ccAddressExpandMode.value = ExpandMode.EXPAND; + break; + case PrefixEmailAddress.bcc: + listBccEmailAddress.remove(draggableEmailAddress.emailAddress); + bccAddressExpandMode.value = ExpandMode.EXPAND; + break; + default: + break; + } + isInitialRecipient.value = true; + isInitialRecipient.refresh(); + + _updateStatusEmailSendButton(); + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 82bd999d2..77f5231c7 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -118,6 +118,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.ccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -138,6 +139,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.bccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -158,6 +160,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), ], )), @@ -259,6 +262,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.ccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -279,6 +283,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.bccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -299,6 +304,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), ], )), diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index bfc768f6a..d0ed0815d 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -90,6 +90,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.ccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -110,6 +111,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.bccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -130,6 +132,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), ], )), @@ -243,6 +246,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.ccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -263,6 +267,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.bccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -283,6 +288,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), ], )), @@ -433,6 +439,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.ccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -453,6 +460,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), if (controller.bccRecipientState.value == PrefixRecipientState.enabled) RecipientComposerWidget( @@ -473,6 +481,7 @@ class ComposerView extends GetWidget { onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), ], )), diff --git a/lib/features/composer/presentation/styles/draggable_recipient_tag_widget_style.dart b/lib/features/composer/presentation/styles/draggable_recipient_tag_widget_style.dart index 3d419c338..2f3339b20 100644 --- a/lib/features/composer/presentation/styles/draggable_recipient_tag_widget_style.dart +++ b/lib/features/composer/presentation/styles/draggable_recipient_tag_widget_style.dart @@ -4,21 +4,15 @@ import 'package:flutter/material.dart'; class DraggableRecipientTagWidgetStyle { static const double radius = 10; - static const double avatarIconSize = 30; - static const double avatarLabelFontSize = 10; + static const double avatarIconSize = 24; + static const double avatarLabelFontSize = 12; - static const Color avatarBackgroundColor = Colors.white; static const Color deleteIconColor = Colors.white; static const Color backgroundColor = AppColor.primaryColor; static const EdgeInsetsGeometry padding = EdgeInsets.symmetric(horizontal: 6, vertical: 3); static const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(horizontal: 8); - static const TextStyle avatarLabelTextStyle = TextStyle( - color: Colors.black, - fontSize: 12, - fontWeight: FontWeight.w500 - ); static const TextStyle labelTextStyle = TextStyle( color: Colors.white, fontSize: 17, diff --git a/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart b/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart index b1a42ae01..c926bd5dd 100644 --- a/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart +++ b/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart @@ -5,9 +5,11 @@ import 'package:flutter/material.dart'; class RecipientComposerWidgetStyle { static const double deleteRecipientFieldIconSize = 20; static const double space = 8; + static const double enableBorderRadius = 10; static const Color borderColor = AppColor.colorLineComposer; static const Color deleteRecipientFieldIconColor = AppColor.colorCollapseMailbox; + static const Color enableBorderColor = AppColor.primaryColor; static const EdgeInsetsGeometry deleteRecipientFieldIconPadding = EdgeInsetsDirectional.all(3); static const EdgeInsetsGeometry prefixButtonPadding = EdgeInsetsDirectional.symmetric(vertical: 3, horizontal: 5); diff --git a/lib/features/composer/presentation/styles/recipient_tag_item_widget_style.dart b/lib/features/composer/presentation/styles/recipient_tag_item_widget_style.dart index d548a6878..1585ada19 100644 --- a/lib/features/composer/presentation/styles/recipient_tag_item_widget_style.dart +++ b/lib/features/composer/presentation/styles/recipient_tag_item_widget_style.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; class RecipientTagItemWidgetStyle { static const double radius = 10; + static const double avatarIconSize = 24; + static const double avatarLabelFontSize = 12; static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.only(start: 4); static const EdgeInsetsGeometry counterPadding = EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8); diff --git a/lib/features/composer/presentation/widgets/draggable_recipient_tag_widget.dart b/lib/features/composer/presentation/widgets/draggable_recipient_tag_widget.dart index 4a8338fc4..158edfe24 100644 --- a/lib/features/composer/presentation/widgets/draggable_recipient_tag_widget.dart +++ b/lib/features/composer/presentation/widgets/draggable_recipient_tag_widget.dart @@ -1,5 +1,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/extensions/string_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/avatar/gradient_circle_avatar_icon.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -34,16 +36,11 @@ class DraggableRecipientTagWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (emailAddress.displayName.isNotEmpty) - SizedBox( - width: DraggableRecipientTagWidgetStyle.avatarIconSize, - height: DraggableRecipientTagWidgetStyle.avatarIconSize, - child: CircleAvatar( - backgroundColor: DraggableRecipientTagWidgetStyle.avatarBackgroundColor, - child: Text( - emailAddress.displayName[0].toUpperCase(), - style: DraggableRecipientTagWidgetStyle.avatarLabelTextStyle - ) - ), + GradientCircleAvatarIcon( + colors: emailAddress.avatarColors, + label: emailAddress.displayName.firstLetterToUpperCase, + labelFontSize: DraggableRecipientTagWidgetStyle.avatarLabelFontSize, + iconSize: DraggableRecipientTagWidgetStyle.avatarIconSize, ), Padding( padding: DraggableRecipientTagWidgetStyle.labelPadding, diff --git a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart index 11bcf293f..8c7257536 100644 --- a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart @@ -14,11 +14,12 @@ import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/expand_mode.dart'; import 'package:super_tag_editor/tag_editor.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/recipient_composer_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_tag_item_widget.dart'; -import 'package:tmail_ui_user/features/composer/presentation/widgets/suggestion_email_address.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_constants.dart'; @@ -28,6 +29,8 @@ typedef OnAddEmailAddressTypeAction = void Function(PrefixEmailAddress prefix); typedef OnDeleteEmailAddressTypeAction = void Function(PrefixEmailAddress prefix); typedef OnShowFullListEmailAddressAction = void Function(PrefixEmailAddress prefix); typedef OnFocusEmailAddressChangeAction = void Function(PrefixEmailAddress prefix, bool isFocus); +typedef OnRemoveDraggableEmailAddressAction = void Function(DraggableEmailAddress draggableEmailAddress); +typedef OnDeleteTagAction = void Function(EmailAddress emailAddress); class RecipientComposerWidget extends StatefulWidget { @@ -48,6 +51,7 @@ class RecipientComposerWidget extends StatefulWidget { final OnDeleteEmailAddressTypeAction? onDeleteEmailAddressTypeAction; final OnShowFullListEmailAddressAction? onShowFullListEmailAddressAction; final OnFocusEmailAddressChangeAction? onFocusEmailAddressChangeAction; + final OnRemoveDraggableEmailAddressAction? onRemoveDraggableEmailAddressAction; final VoidCallback? onFocusNextAddressAction; final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? margin; @@ -74,6 +78,7 @@ class RecipientComposerWidget extends StatefulWidget { this.onShowFullListEmailAddressAction, this.onFocusEmailAddressChangeAction, this.onFocusNextAddressAction, + this.onRemoveDraggableEmailAddressAction, }); @override @@ -84,6 +89,7 @@ class _RecipientComposerWidgetState extends State { Timer? _gapBetweenTagChangedAndFindSuggestion; bool _lastTagFocused = false; + bool _isDragging = false; late List _currentListEmailAddress; final _imagePaths = Get.find(); @@ -94,6 +100,14 @@ class _RecipientComposerWidgetState extends State { _currentListEmailAddress = widget.listEmailAddress; } + @override + void didUpdateWidget(covariant RecipientComposerWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.listEmailAddress != widget.listEmailAddress) { + _currentListEmailAddress = widget.listEmailAddress; + } + } + @override Widget build(BuildContext context) { return Container( @@ -130,100 +144,82 @@ class _RecipientComposerWidgetState extends State { } return KeyEventResult.ignored; }, - child: TagEditor( - key: widget.keyTagEditor, - length: _collapsedListEmailAddress.length, - controller: widget.controller, - focusNode: widget.focusNode, - autoDisposeFocusNode: widget.autoDisposeFocusNode, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - debounceDuration: const Duration(milliseconds: 150), - hasAddButton: false, - tagSpacing: 8, - autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, - minTextFieldWidth: 20, - resetTextOnSubmitted: true, - suggestionsBoxElevation: 20.0, - suggestionsBoxBackgroundColor: Colors.white, - suggestionsBoxRadius: 20, - suggestionsBoxMaxHeight: 350, - textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) { - setState(() { - _lastTagFocused = focused; - }); - }, - onDeleteTagAction: () { - if (_currentListEmailAddress.isNotEmpty) { - setState(_currentListEmailAddress.removeLast); - widget.onUpdateListEmailAddressAction?.call(widget.prefix, _currentListEmailAddress); - } - }, - onSelectOptionAction: (item) { - if (!_isDuplicatedRecipient(item.emailAddress.emailAddress)) { - setState(() => _currentListEmailAddress.add(item.emailAddress)); - widget.onUpdateListEmailAddressAction?.call(widget.prefix, _currentListEmailAddress); - } - }, - onSubmitted: (value) { - final textTrim = value.trim(); - if (!_isDuplicatedRecipient(textTrim)) { - setState(() => _currentListEmailAddress.add(EmailAddress(null, textTrim))); - widget.onUpdateListEmailAddressAction?.call(widget.prefix, _currentListEmailAddress); - } - }, - inputDecoration: const InputDecoration(border: InputBorder.none), - tagBuilder: (context, index) { - final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; + child: StatefulBuilder( + builder: (context, stateSetter) { + return DragTarget( + builder: (context, candidateData, rejectedData) { + return TagEditor( + key: widget.keyTagEditor, + length: _collapsedListEmailAddress.length, + controller: widget.controller, + focusNode: widget.focusNode, + enableBorder: _isDragging, + borderRadius: RecipientComposerWidgetStyle.enableBorderRadius, + enableBorderColor: RecipientComposerWidgetStyle.enableBorderColor, + autoDisposeFocusNode: widget.autoDisposeFocusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + debounceDuration: const Duration(milliseconds: 150), + hasAddButton: false, + tagSpacing: 8, + autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, + minTextFieldWidth: 20, + resetTextOnSubmitted: true, + suggestionsBoxElevation: 20.0, + suggestionsBoxBackgroundColor: Colors.white, + suggestionsBoxRadius: 20, + suggestionsBoxMaxHeight: 350, + textStyle: RecipientComposerWidgetStyle.inputTextStyle, + onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), + onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), + onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), + inputDecoration: const InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) { + final currentEmailAddress = _currentListEmailAddress[index]; + final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; - return RecipientTagItemWidget( - prefix: widget.prefix, - currentEmailAddress: currentEmailAddress, - currentListEmailAddress: _currentListEmailAddress, - collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, - isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, - onDeleteTagAction: () { - setState(() => _currentListEmailAddress.removeAt(index)); - widget.onUpdateListEmailAddressAction?.call(widget.prefix, _currentListEmailAddress); + return RecipientTagItemWidget( + prefix: widget.prefix, + currentEmailAddress: currentEmailAddress, + currentListEmailAddress: _currentListEmailAddress, + collapsedListEmailAddress: _collapsedListEmailAddress, + isLatestEmail: isLatestEmail, + isCollapsed: _isCollapse, + isLatestTagFocused: _lastTagFocused, + onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), + onShowFullAction: widget.onShowFullListEmailAddressAction, + ); + }, + onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), + findSuggestions: _findSuggestions, + useDefaultHighlight: false, + suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { + return RecipientSuggestionItemWidget( + suggestionState: suggestionEmailAddress.state, + emailAddress: suggestionEmailAddress.emailAddress, + suggestionValid: suggestionValid, + highlight: highlight, + onSelectedAction: (emailAddress) { + stateSetter(() => _currentListEmailAddress.add(emailAddress)); + _updateListEmailAddressAction(); + tagEditorState.resetTextField(); + tagEditorState.closeSuggestionBox(); + }, + ); + }, + ); }, - onShowFullAction: widget.onShowFullListEmailAddressAction, - ); - }, - onTagChanged: (value) { - final textTrim = value.trim(); - if (!_isDuplicatedRecipient(textTrim)) { - setState(() => _currentListEmailAddress.add(EmailAddress(null, textTrim))); - widget.onUpdateListEmailAddressAction?.call(widget.prefix, _currentListEmailAddress); - } - _gapBetweenTagChangedAndFindSuggestion = Timer( - const Duration(seconds: 1), - _handleGapBetweenTagChangedAndFindSuggestion); - }, - findSuggestions: _findSuggestions, - useDefaultHighlight: false, - suggestionBuilder: ( - context, - tagEditorState, - suggestionEmailAddress, - index, - length, - highlight, - suggestionValid - ) { - return RecipientSuggestionItemWidget( - suggestionState: suggestionEmailAddress.state, - emailAddress: suggestionEmailAddress.emailAddress, - suggestionValid: suggestionValid, - highlight: highlight, - onSelectedAction: (emailAddress) { - setState(() => _currentListEmailAddress.add(emailAddress)); - widget.onUpdateListEmailAddressAction?.call(widget.prefix, _currentListEmailAddress); - tagEditorState.resetTextField(); - tagEditorState.closeSuggestionBox(); + onAccept: (draggableEmailAddress) => _handleAcceptDraggableEmailAddressAction(draggableEmailAddress, stateSetter), + onLeave: (draggableEmailAddress) { + if (_isDragging) { + stateSetter(() => _isDragging = false); + } + }, + onMove: (details) { + if (!_isDragging) { + stateSetter(() => _isDragging = true); + } }, ); }, @@ -327,6 +323,92 @@ class _RecipientComposerWidgetState extends State { } void _handleGapBetweenTagChangedAndFindSuggestion() { - log('EmailAddressInputBuilder::_handleGapBetweenTagChangedAndFindSuggestion(): Timeout'); + log('_RecipientComposerWidgetState::_handleGapBetweenTagChangedAndFindSuggestion:Timeout'); + } + + void _updateListEmailAddressAction() { + widget.onUpdateListEmailAddressAction?.call( + widget.prefix, + _currentListEmailAddress + ); + } + + void _handleFocusTagAction(bool focused, StateSetter stateSetter) { + stateSetter(() => _lastTagFocused = focused); + } + + void _handleDeleteLatestTagAction(StateSetter stateSetter) { + if (_currentListEmailAddress.isNotEmpty) { + stateSetter(_currentListEmailAddress.removeLast); + _updateListEmailAddressAction(); + } + } + + void _handleDeleteTagAction(EmailAddress emailAddress, StateSetter stateSetter) { + if (_currentListEmailAddress.isNotEmpty) { + stateSetter(() => _currentListEmailAddress.remove(emailAddress)); + _updateListEmailAddressAction(); + } + } + + void _handleSelectOptionAction( + SuggestionEmailAddress suggestionEmailAddress, + StateSetter stateSetter + ) { + if (!_isDuplicatedRecipient(suggestionEmailAddress.emailAddress.emailAddress)) { + stateSetter(() => _currentListEmailAddress.add(suggestionEmailAddress.emailAddress)); + _updateListEmailAddressAction(); + } + } + + void _handleSubmitTagAction( + String value, + StateSetter stateSetter + ) { + final textTrim = value.trim(); + if (!_isDuplicatedRecipient(textTrim)) { + stateSetter(() => _currentListEmailAddress.add(EmailAddress(null, textTrim))); + _updateListEmailAddressAction(); + } + } + + void _handleOnTagChangeAction( + String value, + StateSetter stateSetter + ) { + final textTrim = value.trim(); + if (!_isDuplicatedRecipient(textTrim)) { + stateSetter(() => _currentListEmailAddress.add(EmailAddress(null, textTrim))); + _updateListEmailAddressAction(); + } + _gapBetweenTagChangedAndFindSuggestion = Timer( + const Duration(seconds: 1), + _handleGapBetweenTagChangedAndFindSuggestion + ); + } + + void _handleAcceptDraggableEmailAddressAction( + DraggableEmailAddress draggableEmailAddress, + StateSetter stateSetter + ) { + log('_RecipientComposerWidgetState::_handleAcceptDraggableEmailAddressAction: $draggableEmailAddress'); + if (draggableEmailAddress.prefix != widget.prefix) { + if (!_currentListEmailAddress.contains(draggableEmailAddress.emailAddress)) { + stateSetter(() { + _currentListEmailAddress.add(draggableEmailAddress.emailAddress); + _isDragging = false; + }); + _updateListEmailAddressAction(); + } else { + if (_isDragging) { + stateSetter(() => _isDragging = false); + } + } + widget.onRemoveDraggableEmailAddressAction?.call(draggableEmailAddress); + } else { + if (_isDragging) { + stateSetter(() => _isDragging = false); + } + } } } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart b/lib/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart index 9d47fffcc..db96fa245 100644 --- a/lib/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart @@ -7,8 +7,8 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:super_tag_editor/widgets/rich_text_widget.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/recipient_suggestion_item_widget_style.dart'; -import 'package:tmail_ui_user/features/composer/presentation/widgets/suggestion_email_address.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/avatar_suggestion_item_widget.dart'; typedef OnSelectedRecipientSuggestionAction = Function(EmailAddress emailAddress); diff --git a/lib/features/composer/presentation/widgets/recipient_tag_item_widget.dart b/lib/features/composer/presentation/widgets/recipient_tag_item_widget.dart index 885591a88..a36190ebf 100644 --- a/lib/features/composer/presentation/widgets/recipient_tag_item_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_tag_item_widget.dart @@ -12,7 +12,9 @@ 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:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/recipient_tag_item_widget_style.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/draggable_recipient_tag_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart'; class RecipientTagItemWidget extends StatelessWidget { @@ -25,7 +27,7 @@ class RecipientTagItemWidget extends StatelessWidget { final List currentListEmailAddress; final List collapsedListEmailAddress; final OnShowFullListEmailAddressAction? onShowFullAction; - final VoidCallback? onDeleteTagAction; + final OnDeleteTagAction? onDeleteTagAction; final _imagePaths = Get.find(); @@ -55,33 +57,41 @@ class RecipientTagItemWidget extends StatelessWidget { onTap: () => isCollapsed ? onShowFullAction?.call(prefix) : null, - child: Chip( - labelPadding: EdgeInsetsDirectional.symmetric( - horizontal: 4, - vertical: DirectionUtils.isDirectionRTLByHasAnyRtl(currentEmailAddress.asString()) ? 0 : 2 + child: Draggable( + data: DraggableEmailAddress(emailAddress: currentEmailAddress, prefix: prefix), + feedback: DraggableRecipientTagWidget(emailAddress: currentEmailAddress), + childWhenDragging: DraggableRecipientTagWidget(emailAddress: currentEmailAddress), + child: MouseRegion( + cursor: SystemMouseCursors.grab, + child: Chip( + labelPadding: EdgeInsetsDirectional.symmetric( + horizontal: 4, + vertical: DirectionUtils.isDirectionRTLByHasAnyRtl(currentEmailAddress.asString()) ? 0 : 2 + ), + label: Text( + currentEmailAddress.asString(), + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + ), + deleteIcon: SvgPicture.asset(_imagePaths.icClose, fit: BoxFit.fill), + labelStyle: RecipientTagItemWidgetStyle.labelTextStyle, + backgroundColor: _getTagBackgroundColor(), + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(RecipientTagItemWidgetStyle.radius)), + side: _getTagBorderSide(), + ), + avatar: currentEmailAddress.displayName.isNotEmpty + ? GradientCircleAvatarIcon( + colors: currentEmailAddress.avatarColors, + label: currentEmailAddress.displayName.firstLetterToUpperCase, + labelFontSize: RecipientTagItemWidgetStyle.avatarLabelFontSize, + iconSize: RecipientTagItemWidgetStyle.avatarIconSize, + ) + : null, + onDeleted: () => onDeleteTagAction?.call(currentEmailAddress), + ), ), - label: Text( - currentEmailAddress.asString(), - maxLines: 1, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - ), - deleteIcon: SvgPicture.asset(_imagePaths.icClose, fit: BoxFit.fill), - labelStyle: RecipientTagItemWidgetStyle.labelTextStyle, - backgroundColor: _getTagBackgroundColor(), - shape: RoundedRectangleBorder( - borderRadius: const BorderRadius.all(Radius.circular(RecipientTagItemWidgetStyle.radius)), - side: _getTagBorderSide(), - ), - avatar: currentEmailAddress.displayName.isNotEmpty - ? GradientCircleAvatarIcon( - colors: currentEmailAddress.avatarColors, - label: currentEmailAddress.displayName.firstLetterToUpperCase, - labelFontSize: 10, - iconSize: 24, - ) - : null, - onDeleted: onDeleteTagAction, ) ), ), diff --git a/pubspec.lock b/pubspec.lock index cbd1e5345..201532e2d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1527,10 +1527,11 @@ packages: super_tag_editor: dependency: "direct main" description: - name: super_tag_editor - sha256: "1bd2e25fb147a2144b3451a8489b35f5cad09f27fe2dca3c0f5cbe4ae12d3047" - url: "https://pub.dev" - source: hosted + path: "." + ref: master + resolved-ref: "22fcc31f816ed62ffa1ef38c2103333b7f001345" + url: "https://github.com/dab246/super_tag_editor.git" + source: git version: "0.2.0" syncfusion_flutter_core: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index e59e2d878..646ecb017 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,6 +81,11 @@ dependencies: url: https://github.com/linagora/flutter-date-range-picker.git ref: master + super_tag_editor: + git: + url: https://github.com/dab246/super_tag_editor.git + ref: master + ### Dependencies from pub.dev ### cupertino_icons: 1.0.5 @@ -202,8 +207,6 @@ dependencies: flutter_linkify: 6.0.0 - super_tag_editor: 0.2.0 - flutter_slidable: 3.0.0 url_strategy: 0.2.0