From cf40d22cc8643aa438a8c898114a0e824d4e8283 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 11 Sep 2025 16:43:23 +0700 Subject: [PATCH] TF-4011 Support arrow keys on keyboard to get again focus after deleting a recipient --- ...fault_autocomplete_input_field_widget.dart | 21 ++++++------ .../default_autocomplete_tag_item_widget.dart | 10 +++--- .../widgets/recipient_composer_widget.dart | 32 +++++++++---------- .../widgets/recipient_tag_item_widget.dart | 10 +++--- .../widgets/contact_input_tag_item.dart | 10 +++--- ...complete_contact_text_field_with_tags.dart | 17 +++++----- pubspec.lock | 4 +-- pubspec.yaml | 2 +- 8 files changed, 51 insertions(+), 55 deletions(-) diff --git a/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart b/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart index ddd8f8c94..ce094b352 100644 --- a/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart +++ b/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart @@ -83,7 +83,7 @@ class _DefaultAutocompleteInputFieldWidgetState extends State { final _imagePaths = Get.find(); - bool _lastTagFocused = false; + int _tagIndexFocused = -1; bool _isDragging = false; late List _currentListEmailAddress; Timer? _gapBetweenTagChangedAndFindSuggestion; @@ -157,17 +157,14 @@ class _DefaultAutocompleteInputFieldWidgetState onSubmitted: _handleSubmitTagAction, tagBuilder: (context, index) { final currentEmailAddress = _currentListEmailAddress.elementAt(index); - final isLatestEmail = - currentEmailAddress == _currentListEmailAddress.last; return DefaultAutocompleteTagItemWidget( field: widget.field, currentEmailAddress: currentEmailAddress, currentListEmailAddress: _currentListEmailAddress, collapsedListEmailAddress: _collapsedListEmailAddress, iconClose: _imagePaths.icClose, - isLatestEmail: isLatestEmail, isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, + isTagFocused: _tagIndexFocused == index, onDeleteTagAction: _handleDeleteTagAction, onShowFullAction: widget.onShowFullListEmailAddressAction, ); @@ -335,13 +332,17 @@ class _DefaultAutocompleteInputFieldWidgetState ); } - void _handleFocusTagAction(bool focused) { - _setStateSafety(() => _lastTagFocused = focused); + void _handleFocusTagAction(int index) { + _setStateSafety(() => _tagIndexFocused = index); } - void _handleDeleteLatestTagAction() { - if (_currentListEmailAddress.isNotEmpty) { - _setStateSafety(_currentListEmailAddress.removeLast); + void _handleDeleteLatestTagAction(int index) { + if (_currentListEmailAddress.isNotEmpty && + index >= 0 && + index < _currentListEmailAddress.length) { + _setStateSafety(() { + _currentListEmailAddress.removeAt(index); + }); _updateListEmailAddressAction(); } } diff --git a/lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart b/lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart index df3c8c32f..2e5b6ce7c 100644 --- a/lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart +++ b/lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart @@ -16,8 +16,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/ad class DefaultAutocompleteTagItemWidget extends StatelessWidget { final bool isCollapsed; - final bool isLatestTagFocused; - final bool isLatestEmail; + final bool isTagFocused; final FilterField field; final EmailAddress currentEmailAddress; final String iconClose; @@ -34,8 +33,7 @@ class DefaultAutocompleteTagItemWidget extends StatelessWidget { required this.collapsedListEmailAddress, required this.iconClose, this.isCollapsed = false, - this.isLatestTagFocused = false, - this.isLatestEmail = false, + this.isTagFocused = false, this.onDeleteTagAction, this.onShowFullAction, }) : super(key: key); @@ -141,7 +139,7 @@ class DefaultAutocompleteTagItemWidget extends StatelessWidget { } Color _getTagBackgroundColor() { - if (isLatestTagFocused && isLatestEmail) { + if (isTagFocused) { return AppColor.colorItemRecipientSelected; } else { return AppColor.colorEmailAddressTag; @@ -149,7 +147,7 @@ class DefaultAutocompleteTagItemWidget extends StatelessWidget { } BorderSide _getTagBorderSide() { - if (isLatestTagFocused && isLatestEmail) { + if (isTagFocused) { return const BorderSide(width: 1, color: AppColor.primaryColor); } else { return const BorderSide(width: 0, color: AppColor.colorEmailAddressTag); diff --git a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart index 3058c3130..8c422a167 100644 --- a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart @@ -126,7 +126,7 @@ class _RecipientComposerWidgetState extends State { final _responsiveUtils = Get.find(); Timer? _gapBetweenTagChangedAndFindSuggestion; - bool _lastTagFocused = false; + int _tagIndexFocused = -1; bool _isDragging = false; late List _currentListEmailAddress; @@ -204,8 +204,8 @@ class _RecipientComposerWidgetState extends State { suggestionItemHeight: RecipientComposerWidgetStyle.suggestionBoxItemHeight, suggestionBoxWidth: _getSuggestionBoxWidth(widget.maxWidth), textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onFocusTagAction: (index) => _handleFocusTagAction.call(index, stateSetter), + onDeleteTagAction: (index) => _handleDeleteLatestTagAction.call(index, stateSetter), onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), onTapOutside: (_) {}, @@ -217,7 +217,6 @@ class _RecipientComposerWidgetState extends State { inputDecoration: const InputDecoration(border: InputBorder.none), tagBuilder: (context, index) { final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; return RecipientTagItemWidget( index: index, @@ -227,9 +226,8 @@ class _RecipientComposerWidgetState extends State { currentEmailAddress: currentEmailAddress, currentListEmailAddress: _currentListEmailAddress, collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, + isTagFocused: _tagIndexFocused == index, maxWidth: widget.maxWidth, isMobile: _responsiveUtils.isMobile(context), onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), @@ -299,8 +297,8 @@ class _RecipientComposerWidgetState extends State { suggestionItemHeight: RecipientComposerWidgetStyle.suggestionBoxItemHeight, suggestionBoxWidth: _getSuggestionBoxWidth(widget.maxWidth), textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onFocusTagAction: (index) => _handleFocusTagAction.call(index, stateSetter), + onDeleteTagAction: (index) => _handleDeleteLatestTagAction.call(index, stateSetter), onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), onTapOutside: (_) {}, @@ -312,7 +310,6 @@ class _RecipientComposerWidgetState extends State { inputDecoration: const InputDecoration(border: InputBorder.none), tagBuilder: (context, index) { final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; return RecipientTagItemWidget( index: index, @@ -322,9 +319,8 @@ class _RecipientComposerWidgetState extends State { currentEmailAddress: currentEmailAddress, currentListEmailAddress: _currentListEmailAddress, collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, + isTagFocused: _tagIndexFocused == index, maxWidth: widget.maxWidth, isMobile: _responsiveUtils.isMobile(context), onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), @@ -525,13 +521,17 @@ class _RecipientComposerWidgetState extends State { ); } - void _handleFocusTagAction(bool focused, StateSetter stateSetter) { - stateSetter(() => _lastTagFocused = focused); + void _handleFocusTagAction(int index, StateSetter stateSetter) { + stateSetter(() => _tagIndexFocused = index); } - void _handleDeleteLatestTagAction(StateSetter stateSetter) { - if (_currentListEmailAddress.isNotEmpty) { - stateSetter(_currentListEmailAddress.removeLast); + void _handleDeleteLatestTagAction(int index, StateSetter stateSetter) { + if (_currentListEmailAddress.isNotEmpty && + index >= 0 && + index < _currentListEmailAddress.length) { + stateSetter(() { + _currentListEmailAddress.removeAt(index); + }); _updateListEmailAddressAction(); } } 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 add9a82c4..71cb35853 100644 --- a/lib/features/composer/presentation/widgets/recipient_tag_item_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_tag_item_widget.dart @@ -23,8 +23,7 @@ import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart class RecipientTagItemWidget extends StatelessWidget { final bool isCollapsed; - final bool isLatestTagFocused; - final bool isLatestEmail; + final bool isTagFocused; final ImagePaths imagePaths; final double? maxWidth; final int index; @@ -50,8 +49,7 @@ class RecipientTagItemWidget extends StatelessWidget { required this.imagePaths, @visibleForTesting this.isTestingForWeb = false, this.isCollapsed = false, - this.isLatestTagFocused = false, - this.isLatestEmail = false, + this.isTagFocused = false, this.isMobile = false, this.onShowFullAction, this.onDeleteTagAction, @@ -195,7 +193,7 @@ class RecipientTagItemWidget extends StatelessWidget { int get countRecipients => currentListEmailAddress.length - collapsedListEmailAddress.length; Color _getTagBackgroundColor() { - if (isLatestTagFocused && isLatestEmail) { + if (isTagFocused) { return AppColor.colorItemRecipientSelected; } else if (EmailUtils.isEmailAddressValid(currentEmailAddress.emailAddress)) { return AppColor.grayBackgroundColor; @@ -205,7 +203,7 @@ class RecipientTagItemWidget extends StatelessWidget { } BorderSide _getTagBorderSide() { - if (isLatestTagFocused && isLatestEmail) { + if (isTagFocused) { return const BorderSide(width: 1, color: AppColor.primaryColor); } else if (EmailUtils.isEmailAddressValid(currentEmailAddress.emailAddress)) { return const BorderSide(width: 1, color: AppColor.colorEmailAddressTag); diff --git a/lib/features/contact/presentation/widgets/contact_input_tag_item.dart b/lib/features/contact/presentation/widgets/contact_input_tag_item.dart index 8641fcd2b..1383ebfc7 100644 --- a/lib/features/contact/presentation/widgets/contact_input_tag_item.dart +++ b/lib/features/contact/presentation/widgets/contact_input_tag_item.dart @@ -18,16 +18,14 @@ typedef DeleteContactCallbackAction = Function(EmailAddress contactDeleted); class ContactInputTagItem extends StatelessWidget { final EmailAddress contact; - final bool isLastContact; - final bool lastTagFocused; + final bool isTagFocused; final DeleteContactCallbackAction? deleteContactCallbackAction; const ContactInputTagItem( this.contact, { Key? key, - this.isLastContact = false, - this.lastTagFocused = false, + this.isTagFocused = false, this.deleteContactCallbackAction } ) : super(key: key); @@ -97,7 +95,7 @@ class ContactInputTagItem extends StatelessWidget { bool _isValidEmailAddress(String value) => value.isEmail || AppUtils.isEmailLocalhost(value); Color _getTagBackgroundColor() { - if (lastTagFocused && isLastContact) { + if (isTagFocused) { return AppColor.colorItemRecipientSelected; } else { return _isValidEmailAddress(contact.emailAddress) @@ -107,7 +105,7 @@ class ContactInputTagItem extends StatelessWidget { } BorderSide _getTagBorderSide() { - if (lastTagFocused && isLastContact) { + if (isTagFocused) { return const BorderSide( width: 1, color: AppColor.primaryColor); diff --git a/lib/features/manage_account/presentation/forward/widgets/autocomplete_contact_text_field_with_tags.dart b/lib/features/manage_account/presentation/forward/widgets/autocomplete_contact_text_field_with_tags.dart index 733a2f3b3..d6c63494c 100644 --- a/lib/features/manage_account/presentation/forward/widgets/autocomplete_contact_text_field_with_tags.dart +++ b/lib/features/manage_account/presentation/forward/widgets/autocomplete_contact_text_field_with_tags.dart @@ -67,7 +67,7 @@ class _AutocompleteContactTextFieldWithTagsState extends State listEmailAddress; - bool lastTagFocused = false; + int _tagIndexFocused = -1; @override void initState() { @@ -107,15 +107,17 @@ class _AutocompleteContactTextFieldWithTagsState extends State= 0 && + index < listEmailAddress.length) { setState(() { - listEmailAddress.removeLast(); + listEmailAddress.removeAt(index); }); } }, @@ -136,8 +138,7 @@ class _AutocompleteContactTextFieldWithTagsState extends State ContactInputTagItem( listEmailAddress[index], - isLastContact: index == listEmailAddress.length - 1, - lastTagFocused: lastTagFocused, + isTagFocused: _tagIndexFocused == index, deleteContactCallbackAction: (contact) { setState(() => listEmailAddress.remove(contact)); } diff --git a/pubspec.lock b/pubspec.lock index ba227c40f..293c4ac16 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2083,10 +2083,10 @@ packages: dependency: "direct main" description: name: super_tag_editor - sha256: "291b4b5ef5c44d1d06dc0337f66b71f7afd77b8c98d0ad0f4f8baca6a6f3cbc4" + sha256: fbf81653bc01a89e6fe63b9ae01cc12d557020cf74c870ed49e8e406ad710aac url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.3.3" syncfusion_flutter_core: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 64f3271af..8f23a7754 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -110,7 +110,7 @@ dependencies: ref: master ### Dependencies from pub.dev ### - super_tag_editor: 0.3.2 + super_tag_editor: 0.3.3 cupertino_icons: 1.0.6