TF-4011 Support arrow keys on keyboard to get again focus after deleting a recipient

This commit is contained in:
dab246
2025-09-11 16:43:23 +07:00
committed by Dat H. Pham
parent 9abde49c62
commit cf40d22cc8
8 changed files with 51 additions and 55 deletions
@@ -83,7 +83,7 @@ class _DefaultAutocompleteInputFieldWidgetState
extends State<DefaultAutocompleteInputFieldWidget> {
final _imagePaths = Get.find<ImagePaths>();
bool _lastTagFocused = false;
int _tagIndexFocused = -1;
bool _isDragging = false;
late List<EmailAddress> _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();
}
}
@@ -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);
@@ -126,7 +126,7 @@ class _RecipientComposerWidgetState extends State<RecipientComposerWidget> {
final _responsiveUtils = Get.find<ResponsiveUtils>();
Timer? _gapBetweenTagChangedAndFindSuggestion;
bool _lastTagFocused = false;
int _tagIndexFocused = -1;
bool _isDragging = false;
late List<EmailAddress> _currentListEmailAddress;
@@ -204,8 +204,8 @@ class _RecipientComposerWidgetState extends State<RecipientComposerWidget> {
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<RecipientComposerWidget> {
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<RecipientComposerWidget> {
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<RecipientComposerWidget> {
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<RecipientComposerWidget> {
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<RecipientComposerWidget> {
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<RecipientComposerWidget> {
);
}
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();
}
}
@@ -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);
@@ -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);
@@ -67,7 +67,7 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
late List<EmailAddress> listEmailAddress;
bool lastTagFocused = false;
int _tagIndexFocused = -1;
@override
void initState() {
@@ -107,15 +107,17 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
suggestionsBoxBackgroundColor: Colors.white,
suggestionsBoxRadius: 16,
suggestionsBoxMaxHeight: 350,
onFocusTagAction: (focused) {
onFocusTagAction: (index) {
setState(() {
lastTagFocused = focused;
_tagIndexFocused = index;
});
},
onDeleteTagAction: () {
if (listEmailAddress.isNotEmpty) {
onDeleteTagAction: (index) {
if (listEmailAddress.isNotEmpty &&
index >= 0 &&
index < listEmailAddress.length) {
setState(() {
listEmailAddress.removeLast();
listEmailAddress.removeAt(index);
});
}
},
@@ -136,8 +138,7 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
),
tagBuilder: (context, index) => ContactInputTagItem(
listEmailAddress[index],
isLastContact: index == listEmailAddress.length - 1,
lastTagFocused: lastTagFocused,
isTagFocused: _tagIndexFocused == index,
deleteContactCallbackAction: (contact) {
setState(() => listEmailAddress.remove(contact));
}
+2 -2
View File
@@ -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:
+1 -1
View File
@@ -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