From 6b1d967861f5d77780db180c6d18e7a3c3408c10 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 4 Aug 2022 15:20:18 +0700 Subject: [PATCH] TF-691 Support change focus input field by `TAB` key on web keyboard --- .../views/button/icon_button_web.dart | 2 + .../advanced_filter_controller.dart | 4 + .../controller/input_field_focus_manager.dart | 40 ++++++ .../advanced_search_filter_form.dart | 117 +++++++++++----- ...vanced_search_filter_form_bottom_view.dart | 125 ++++++++++++----- ...text_field_auto_complete_email_adress.dart | 130 ++++++++++-------- 6 files changed, 291 insertions(+), 127 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart diff --git a/core/lib/presentation/views/button/icon_button_web.dart b/core/lib/presentation/views/button/icon_button_web.dart index 4867e85ce..85d99b2b2 100644 --- a/core/lib/presentation/views/button/icon_button_web.dart +++ b/core/lib/presentation/views/button/icon_button_web.dart @@ -109,11 +109,13 @@ Widget buildTextButton(String text, { EdgeInsets? padding, double? radius, IconWebCallback? onTap, + FocusNode? focusNode, }) { return SizedBox( width: width ?? double.infinity, height: height ?? 40, child: ElevatedButton( + focusNode: focusNode, style: ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((states) => backgroundColor ?? AppColor.colorTextButton), elevation: MaterialStateProperty.resolveWith((states) => 0), diff --git a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart index 7b762c8a7..05c042660 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart @@ -11,6 +11,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; @@ -44,6 +45,8 @@ class AdvancedFilterController extends GetxController { SearchEmailFilter get searchEmailFilter => searchController.searchEmailFilter.value; + final focusManager = InputFieldFocusManager.initial(); + @override void onReady() async { if (!BuildUtils.isWeb) { @@ -202,6 +205,7 @@ class AdvancedFilterController extends GetxController { @override void onClose() { + focusManager.dispose(); subjectFilterInputController.dispose(); hasKeyWordFilterInputController.dispose(); notKeyWordFilterInputController.dispose(); diff --git a/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart b/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart new file mode 100644 index 000000000..dcb735e0f --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart @@ -0,0 +1,40 @@ + +import 'package:flutter/material.dart'; + +class InputFieldFocusManager { + + late FocusNode fromFieldFocusNode; + late FocusNode toFieldFocusNode; + late FocusNode subjectFieldFocusNode; + late FocusNode hasKeywordFieldFocusNode; + late FocusNode notKeywordFieldFocusNode; + late FocusNode mailboxFieldFocusNode; + late FocusNode attachmentCheckboxFocusNode; + late FocusNode searchButtonFocusNode; + + InputFieldFocusManager() { + fromFieldFocusNode = FocusNode(); + toFieldFocusNode = FocusNode(); + subjectFieldFocusNode = FocusNode(); + hasKeywordFieldFocusNode = FocusNode(); + notKeywordFieldFocusNode = FocusNode(); + mailboxFieldFocusNode = FocusNode(); + attachmentCheckboxFocusNode = FocusNode(skipTraversal: true); + searchButtonFocusNode = FocusNode(); + } + + factory InputFieldFocusManager.initial() { + return InputFieldFocusManager(); + } + + void dispose() { + fromFieldFocusNode.dispose(); + toFieldFocusNode.dispose(); + subjectFieldFocusNode.dispose(); + hasKeywordFieldFocusNode.dispose(); + notKeywordFieldFocusNode.dispose(); + mailboxFieldFocusNode.dispose(); + attachmentCheckboxFocusNode.dispose(); + searchButtonFocusNode.dispose(); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart index b0fed339b..6c0915502 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart @@ -1,5 +1,6 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart'; @@ -30,33 +31,45 @@ class AdvancedSearchInputForm extends GetWidget context: context, advancedSearchFilterField: AdvancedSearchFilterField.form, listTagInitial: controller.searchEmailFilter.from, + currentFocusNode: controller.focusManager.fromFieldFocusNode, + nextFocusNode: controller.focusManager.toFieldFocusNode ), _buildSuggestionFilterField( listTagSelected: controller.searchEmailFilter.to, context: context, advancedSearchFilterField: AdvancedSearchFilterField.to, listTagInitial: controller.searchEmailFilter.to, + currentFocusNode: controller.focusManager.toFieldFocusNode, + nextFocusNode: controller.focusManager.subjectFieldFocusNode ), _buildFilterField( textEditingController: controller.subjectFilterInputController, context: context, advancedSearchFilterField: AdvancedSearchFilterField.subject, + currentFocusNode: controller.focusManager.subjectFieldFocusNode, + nextFocusNode: controller.focusManager.hasKeywordFieldFocusNode ), _buildFilterField( textEditingController: controller.hasKeyWordFilterInputController, context: context, advancedSearchFilterField: AdvancedSearchFilterField.hasKeyword, + currentFocusNode: controller.focusManager.hasKeywordFieldFocusNode, + nextFocusNode: controller.focusManager.notKeywordFieldFocusNode ), _buildFilterField( textEditingController: controller.notKeyWordFilterInputController, context: context, advancedSearchFilterField: AdvancedSearchFilterField.notKeyword, + currentFocusNode: controller.focusManager.notKeywordFieldFocusNode, + nextFocusNode: controller.focusManager.mailboxFieldFocusNode, ), _buildFilterField( textEditingController: controller.mailBoxFilterInputController, context: context, advancedSearchFilterField: AdvancedSearchFilterField.mailBox, isSelectFormList: true, + currentFocusNode: controller.focusManager.mailboxFieldFocusNode, + nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode, mouseCursor: SystemMouseCursors.click, onTap: () async { await controller.selectedMailBox(); @@ -73,7 +86,7 @@ class AdvancedSearchInputForm extends GetWidget ); }, ), - const AdvancedSearchFilterFormBottomView() + AdvancedSearchFilterFormBottomView(focusManager: controller.focusManager) ], ), ); @@ -120,6 +133,8 @@ class AdvancedSearchInputForm extends GetWidget VoidCallback? onTap, bool isSelectFormList = false, MouseCursor? mouseCursor, + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, }) { final child = [ SizedBox( @@ -140,6 +155,8 @@ class AdvancedSearchInputForm extends GetWidget onTap: onTap, context: context, mouseCursor: mouseCursor, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, advancedSearchFilterField: advancedSearchFilterField, textEditingController: textEditingController, ) @@ -152,6 +169,8 @@ class AdvancedSearchInputForm extends GetWidget onTap: onTap, context: context, mouseCursor: mouseCursor, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, advancedSearchFilterField: advancedSearchFilterField, textEditingController: textEditingController, ) @@ -162,6 +181,8 @@ class AdvancedSearchInputForm extends GetWidget onTap: onTap, context: context, mouseCursor: mouseCursor, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, advancedSearchFilterField: advancedSearchFilterField, textEditingController: textEditingController, ), @@ -187,6 +208,8 @@ class AdvancedSearchInputForm extends GetWidget VoidCallback? onTap, bool isSelectFormList = false, MouseCursor? mouseCursor, + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, }) { switch (advancedSearchFilterField) { case AdvancedSearchFilterField.date: @@ -197,6 +220,8 @@ class AdvancedSearchInputForm extends GetWidget onTap: onTap, context: context, mouseCursor: mouseCursor, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, advancedSearchFilterField: advancedSearchFilterField, textEditingController: textEditingController, ); @@ -208,6 +233,8 @@ class AdvancedSearchInputForm extends GetWidget required BuildContext context, required Set listTagSelected, required Set listTagInitial, + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, }) { final child = [ SizedBox( @@ -229,6 +256,8 @@ class AdvancedSearchInputForm extends GetWidget }, advancedSearchFilterField: advancedSearchFilterField, initialTags: listTagInitial, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, onAddTag: (value) { if (advancedSearchFilterField == AdvancedSearchFilterField.form) { controller.searchEmailFilter.from.add(value.trim()); @@ -263,6 +292,8 @@ class AdvancedSearchInputForm extends GetWidget }, advancedSearchFilterField: advancedSearchFilterField, initialTags: listTagInitial, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, onAddTag: (value) { if (advancedSearchFilterField == AdvancedSearchFilterField.form) { controller.searchEmailFilter.from.add(value.trim()); @@ -312,46 +343,60 @@ class AdvancedSearchInputForm extends GetWidget VoidCallback? onTap, bool isSelectFormList = false, MouseCursor? mouseCursor, + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, }) { - return TextField( - controller: textEditingController, - readOnly: isSelectFormList, - mouseCursor: mouseCursor, - onTap: onTap, - decoration: InputDecoration( - filled: true, - fillColor: AppColor.loginTextFieldBackgroundColor, - contentPadding: const EdgeInsets.only( - right: 8, - left: 12, - ), - enabledBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10), + return RawKeyboardListener( + focusNode: currentFocusNode ?? FocusNode(), + onKey: (event) { + log('AdvancedSearchInputForm::_buildTextField(): Event runtimeType is ${event.runtimeType}'); + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + log('AdvancedSearchInputForm::_buildTextField(): PRESS TAB'); + nextFocusNode?.requestFocus(); + } + }, + child: TextField( + controller: textEditingController, + readOnly: isSelectFormList, + mouseCursor: mouseCursor, + textInputAction: TextInputAction.next, + onTap: onTap, + decoration: InputDecoration( + filled: true, + fillColor: AppColor.loginTextFieldBackgroundColor, + contentPadding: const EdgeInsets.only( + right: 8, + left: 12, ), - borderSide: BorderSide( - width: 0.5, - color: AppColor.colorInputBorderCreateMailbox, + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10), + ), + borderSide: BorderSide( + width: 0.5, + color: AppColor.colorInputBorderCreateMailbox, + ), ), - ), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10), + ), ), + hintText: advancedSearchFilterField.getHintText(context), + hintStyle: const TextStyle( + fontSize: 14, + color: AppColor.colorHintSearchBar, + ), + suffixIconConstraints: const BoxConstraints(minHeight: 24, minWidth: 24), + suffixIcon: isSelectFormList + ? buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icDropDown, + ), + ) + : null, ), - hintText: advancedSearchFilterField.getHintText(context), - hintStyle: const TextStyle( - fontSize: 14, - color: AppColor.colorHintSearchBar, - ), - suffixIconConstraints: const BoxConstraints(minHeight: 24, minWidth: 24), - suffixIcon: isSelectFormList - ? buildIconWeb( - icon: SvgPicture.asset( - _imagePaths.icDropDown, - ), - ) - : null, ), ); } 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 67b4a94b7..4265059e7 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,40 +1,50 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.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'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; -class AdvancedSearchFilterFormBottomView - extends GetWidget { +class AdvancedSearchFilterFormBottomView extends GetWidget { + + final InputFieldFocusManager? focusManager; + const AdvancedSearchFilterFormBottomView({ Key? key, + this.focusManager, }) : super(key: key); @override Widget build(BuildContext context) { - final ResponsiveUtils _responsiveUtils = Get.find(); + final _responsiveUtils = Get.find(); return Padding( padding: EdgeInsets.only( - top: _responsiveUtils.isMobile(context) || - _responsiveUtils.landscapeTabletSupported(context) ? 8 : 20), + top: _isMobileAndLandscapeTablet(context, _responsiveUtils) ? 8 : 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (_responsiveUtils.isMobile(context) || _responsiveUtils.landscapeTabletSupported(context)) + if (_isMobileAndLandscapeTablet(context, _responsiveUtils)) ...[ - _buildCheckboxHasAttachment(context), + _buildCheckboxHasAttachment( + context, + currentFocusNode: focusManager?.attachmentCheckboxFocusNode, + nextFocusNode: focusManager?.searchButtonFocusNode), const SizedBox(height: 24) ], Row( - mainAxisAlignment: _responsiveUtils.isMobile(context) || _responsiveUtils.landscapeTabletSupported(context) + mainAxisAlignment: _isMobileAndLandscapeTablet(context, _responsiveUtils) ? MainAxisAlignment.spaceEvenly : MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (!_responsiveUtils.isMobile(context) && !_responsiveUtils.landscapeTabletSupported(context)) - Expanded(child: _buildCheckboxHasAttachment(context)), + if (!_isMobileAndLandscapeTablet(context, _responsiveUtils)) + Expanded(child: _buildCheckboxHasAttachment( + context, + currentFocusNode: focusManager?.attachmentCheckboxFocusNode, + nextFocusNode: focusManager?.searchButtonFocusNode)), ..._buildListButton(context, _responsiveUtils), ], ), @@ -44,8 +54,10 @@ class AdvancedSearchFilterFormBottomView } List _buildListButton( - BuildContext context, ResponsiveUtils responsiveUtils) { - if (responsiveUtils.isMobile(context) || responsiveUtils.landscapeTabletSupported(context)) { + BuildContext context, + ResponsiveUtils responsiveUtils + ) { + if (_isMobileAndLandscapeTablet(context, responsiveUtils)) { return [ Expanded( child: _buildButton( @@ -72,6 +84,8 @@ class AdvancedSearchFilterFormBottomView text: AppLocalizations.of(context).search, context: context, responsiveUtils: responsiveUtils, + currentFocusNode: focusManager?.searchButtonFocusNode, + nextFocusNode: focusManager?.fromFieldFocusNode ), ), ]; @@ -99,23 +113,43 @@ class AdvancedSearchFilterFormBottomView text: AppLocalizations.of(context).search, context: context, responsiveUtils: responsiveUtils, + currentFocusNode: focusManager?.searchButtonFocusNode, + nextFocusNode: focusManager?.fromFieldFocusNode ), ]; } } - Widget _buildCheckboxHasAttachment(BuildContext context) { + Widget _buildCheckboxHasAttachment( + BuildContext context, + { + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, + } + ) { return Obx( () => SizedBox( width: 220, - child: CheckboxListTile( - contentPadding: EdgeInsets.zero, - controlAffinity: ListTileControlAffinity.leading, - value: controller.hasAttachment.value, - onChanged: (value) { - controller.hasAttachment.value = value ?? false; + child: RawKeyboardListener( + focusNode: FocusNode(), + onKey: (event) { + log('AdvancedSearchFilterFormBottomView::_buildCheckboxHasAttachment(): Event runtimeType is ${event.runtimeType}'); + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + log('AdvancedSearchFilterFormBottomView::_buildCheckboxHasAttachment(): PRESS TAB'); + nextFocusNode?.requestFocus(); + } }, - title: Text(AppLocalizations.of(context).hasAttachment), + child: CheckboxListTile( + focusNode: currentFocusNode, + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.leading, + value: controller.hasAttachment.value, + onChanged: (value) { + controller.hasAttachment.value = value ?? false; + }, + title: Text(AppLocalizations.of(context).hasAttachment), + ), ), ), ); @@ -128,25 +162,42 @@ class AdvancedSearchFilterFormBottomView required VoidCallback onAction, required BuildContext context, required ResponsiveUtils responsiveUtils, + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, }) { - return InkWell( - onTap: onAction, - child: Container( - height: 44, - padding: EdgeInsets.symmetric( - horizontal: responsiveUtils.isMobile(context) || responsiveUtils.landscapeTabletSupported(context) - ? 0 : 26), - constraints: BoxConstraints( - maxWidth: responsiveUtils.isMobile(context) || responsiveUtils.landscapeTabletSupported(context) - ? double.infinity : 144), - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), color: colorButton), - child: Text( + return RawKeyboardListener( + focusNode: FocusNode(), + onKey: (event) { + log('AdvancedSearchFilterFormBottomView::_buildButton(): Event runtimeType is ${event.runtimeType}'); + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + log('AdvancedSearchFilterFormBottomView::_buildButton(): PRESS TAB'); + nextFocusNode?.requestFocus(); + } + }, + child: buildTextButton( text, - style: TextStyle(fontSize: 17, color: colorText), - ), - ), + focusNode: currentFocusNode, + width: _isMobileAndLandscapeTablet(context, responsiveUtils) + ? double.infinity + : 144, + height: 44, + radius: 10, + textStyle: TextStyle(fontSize: 17, color: colorText), + backgroundColor: colorButton, + padding: EdgeInsets.symmetric( + horizontal: _isMobileAndLandscapeTablet(context, responsiveUtils) + ? 0 + : 26), + onTap: onAction), ); } + + bool _isMobileAndLandscapeTablet( + BuildContext context, + ResponsiveUtils responsive + ) { + return responsive.isMobile(context) || + responsive.landscapeTabletSupported(context); + } } diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_auto_complete_email_adress.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_auto_complete_email_adress.dart index 267265a4a..a5e3def8e 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_auto_complete_email_adress.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_auto_complete_email_adress.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:core/core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; @@ -20,6 +21,8 @@ class TextFieldAutoCompleteEmailAddress extends StatefulWidget { required this.onChange, required this.onDeleteTag, required this.onAddTag, + this.currentFocusNode, + this.nextFocusNode, }) : super(key: key); final AdvancedSearchFilterField advancedSearchFilterField; final Set initialTags; @@ -27,6 +30,8 @@ class TextFieldAutoCompleteEmailAddress extends StatefulWidget { final Function(String) onChange; final Function(String) onDeleteTag; final Function(String) onAddTag; + final FocusNode? currentFocusNode; + final FocusNode? nextFocusNode; @override State createState() => @@ -63,7 +68,7 @@ class _TextFieldAutoCompleteEmailAddressState margin: const EdgeInsets.only( top: BuildUtils.isWeb ? 5 : 8, bottom: 16), - height: maxHeightSuggestionBox, + height: _getHeightSuggestionBox(listEmailAddress.length, 65), width: maxWidthSuggestionBox, alignment: Alignment.topLeft, child: Card( @@ -73,12 +78,13 @@ class _TextFieldAutoCompleteEmailAddressState borderRadius: BorderRadius.circular(20.0), child: Container( alignment: Alignment.topCenter, - height: maxHeightSuggestionBox, + height: _getHeightSuggestionBox(listEmailAddress.length, 65), width: maxWidthSuggestionBox, color: Colors.white, child: ListView.builder( shrinkWrap: true, padding: EdgeInsets.zero, + itemExtent: 65, itemCount: listEmailAddress.length, itemBuilder: (BuildContext context, int index) { final emailAddress = listEmailAddress.elementAt(index); @@ -94,7 +100,10 @@ class _TextFieldAutoCompleteEmailAddressState ); }, optionsBuilder: (TextEditingValue textEditingValue) { - return widget.optionsBuilder.call(textEditingValue.text); + if (textEditingValue.text == '') { + return const Iterable.empty(); + } + return widget.optionsBuilder.call(textEditingValue.text.toLowerCase()); }, onSelected: (EmailAddress selectedTag) { _controller.addTag = selectedTag.asString(); @@ -115,53 +124,65 @@ class _TextFieldAutoCompleteEmailAddressState }, inputfieldBuilder: (context, tec, fn, error, onChanged, onSubmitted) { return ((context, sc, tags, onTagDelete) { - return TextField( - controller: tec, - focusNode: fn, - decoration: InputDecoration( - filled: true, - fillColor: AppColor.loginTextFieldBackgroundColor, - contentPadding: const EdgeInsets.only( - right: 8, - left: 12, - ), - enabledBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10), + return RawKeyboardListener( + focusNode: widget.currentFocusNode ?? FocusNode(), + onKey: (event) { + log('_TextFieldAutoCompleteEmailAddressState::inputfieldBuilder(): Event runtimeType is ${event.runtimeType}'); + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + log('_TextFieldAutoCompleteEmailAddressState::inputfieldBuilder(): PRESS TAB'); + widget.nextFocusNode?.requestFocus(); + } + }, + child: TextField( + controller: tec, + focusNode: fn, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + filled: true, + fillColor: AppColor.loginTextFieldBackgroundColor, + contentPadding: const EdgeInsets.only( + right: 8, + left: 12, ), - borderSide: BorderSide( - width: 0.5, - color: AppColor.colorInputBorderCreateMailbox, + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10), + ), + borderSide: BorderSide( + width: 0.5, + color: AppColor.colorInputBorderCreateMailbox, + ), ), - ), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10), + ), ), + hintText: widget.advancedSearchFilterField.getHintText(context), + hintStyle: const TextStyle( + fontSize: 14, + color: AppColor.colorHintSearchBar, + ), + prefixIconConstraints: BoxConstraints(maxWidth: _distanceToField * 0.74), + prefixIcon: tags.isNotEmpty ? SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 10), + controller: sc, + scrollDirection: Axis.horizontal, + child: Row( + children: tags.map((String tag) { + return _buildTagItem(context, tag, onTagDelete); + }).toList()), + ) + : null, ), - hintText: widget.advancedSearchFilterField.getHintText(context), - hintStyle: const TextStyle( - fontSize: 14, - color: AppColor.colorHintSearchBar, - ), - prefixIconConstraints: BoxConstraints(maxWidth: _distanceToField * 0.74), - prefixIcon: tags.isNotEmpty ? SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 10), - controller: sc, - scrollDirection: Axis.horizontal, - child: Row( - children: tags.map((String tag) { - return _buildTagItem(context, tag, onTagDelete); - }).toList()), - ) - : null, + onChanged: (value) { + onChanged?.call(value); + }, + onSubmitted: (tag) { + onSubmitted?.call(tag); + }, ), - onChanged: (value) { - onChanged?.call(value); - }, - onSubmitted: (tag) { - onSubmitted?.call(tag); - }, ); }); }, @@ -177,7 +198,7 @@ class _TextFieldAutoCompleteEmailAddressState return Container( color: Colors.white, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), - child: Row(children: [ + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 40, height: 40, @@ -200,6 +221,7 @@ class _TextFieldAutoCompleteEmailAddressState padding: const EdgeInsets.only(left: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ Text( emailAddress.asString(), @@ -272,10 +294,8 @@ class _TextFieldAutoCompleteEmailAddressState fontSize: 17, fontWeight: FontWeight.normal)), const SizedBox(width: 4), - GestureDetector( - onTap: () { - onTagDelete.call(tag); - }, + InkWell( + onTap: () => onTagDelete.call(tag), child: SvgPicture.asset( _imagePaths.icClose, width: 28, @@ -300,14 +320,16 @@ class _TextFieldAutoCompleteEmailAddressState return 0; } - double get maxHeightSuggestionBox { + double _getHeightSuggestionBox(int countItem, double heightItem) { + final maxHeightList = countItem * heightItem; + if (BuildUtils.isWeb) { - return 250; + return maxHeightList > 250 ? 250 : maxHeightList; } else { if (_responsiveUtils.isLandscapeMobile(context)) { - return 180; + return maxHeightList > 180 ? 180 : maxHeightList; } else { - return 250; + return maxHeightList > 250 ? 250 : maxHeightList; } } }