TF-691 Support change focus input field by TAB key on web keyboard

This commit is contained in:
dab246
2022-08-04 15:20:18 +07:00
committed by Dat H. Pham
parent 6a96117355
commit 6b1d967861
6 changed files with 291 additions and 127 deletions
@@ -109,11 +109,13 @@ Widget buildTextButton(String text, {
EdgeInsets? padding, EdgeInsets? padding,
double? radius, double? radius,
IconWebCallback? onTap, IconWebCallback? onTap,
FocusNode? focusNode,
}) { }) {
return SizedBox( return SizedBox(
width: width ?? double.infinity, width: width ?? double.infinity,
height: height ?? 40, height: height ?? 40,
child: ElevatedButton( child: ElevatedButton(
focusNode: focusNode,
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) => backgroundColor ?? AppColor.colorTextButton), backgroundColor: MaterialStateProperty.resolveWith((states) => backgroundColor ?? AppColor.colorTextButton),
elevation: MaterialStateProperty.resolveWith((states) => 0), elevation: MaterialStateProperty.resolveWith((states) => 0),
@@ -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/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/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/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/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/controller/search_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.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 => SearchEmailFilter get searchEmailFilter =>
searchController.searchEmailFilter.value; searchController.searchEmailFilter.value;
final focusManager = InputFieldFocusManager.initial();
@override @override
void onReady() async { void onReady() async {
if (!BuildUtils.isWeb) { if (!BuildUtils.isWeb) {
@@ -202,6 +205,7 @@ class AdvancedFilterController extends GetxController {
@override @override
void onClose() { void onClose() {
focusManager.dispose();
subjectFilterInputController.dispose(); subjectFilterInputController.dispose();
hasKeyWordFilterInputController.dispose(); hasKeyWordFilterInputController.dispose();
notKeyWordFilterInputController.dispose(); notKeyWordFilterInputController.dispose();
@@ -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();
}
}
@@ -1,5 +1,6 @@
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart';
@@ -30,33 +31,45 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
context: context, context: context,
advancedSearchFilterField: AdvancedSearchFilterField.form, advancedSearchFilterField: AdvancedSearchFilterField.form,
listTagInitial: controller.searchEmailFilter.from, listTagInitial: controller.searchEmailFilter.from,
currentFocusNode: controller.focusManager.fromFieldFocusNode,
nextFocusNode: controller.focusManager.toFieldFocusNode
), ),
_buildSuggestionFilterField( _buildSuggestionFilterField(
listTagSelected: controller.searchEmailFilter.to, listTagSelected: controller.searchEmailFilter.to,
context: context, context: context,
advancedSearchFilterField: AdvancedSearchFilterField.to, advancedSearchFilterField: AdvancedSearchFilterField.to,
listTagInitial: controller.searchEmailFilter.to, listTagInitial: controller.searchEmailFilter.to,
currentFocusNode: controller.focusManager.toFieldFocusNode,
nextFocusNode: controller.focusManager.subjectFieldFocusNode
), ),
_buildFilterField( _buildFilterField(
textEditingController: controller.subjectFilterInputController, textEditingController: controller.subjectFilterInputController,
context: context, context: context,
advancedSearchFilterField: AdvancedSearchFilterField.subject, advancedSearchFilterField: AdvancedSearchFilterField.subject,
currentFocusNode: controller.focusManager.subjectFieldFocusNode,
nextFocusNode: controller.focusManager.hasKeywordFieldFocusNode
), ),
_buildFilterField( _buildFilterField(
textEditingController: controller.hasKeyWordFilterInputController, textEditingController: controller.hasKeyWordFilterInputController,
context: context, context: context,
advancedSearchFilterField: AdvancedSearchFilterField.hasKeyword, advancedSearchFilterField: AdvancedSearchFilterField.hasKeyword,
currentFocusNode: controller.focusManager.hasKeywordFieldFocusNode,
nextFocusNode: controller.focusManager.notKeywordFieldFocusNode
), ),
_buildFilterField( _buildFilterField(
textEditingController: controller.notKeyWordFilterInputController, textEditingController: controller.notKeyWordFilterInputController,
context: context, context: context,
advancedSearchFilterField: AdvancedSearchFilterField.notKeyword, advancedSearchFilterField: AdvancedSearchFilterField.notKeyword,
currentFocusNode: controller.focusManager.notKeywordFieldFocusNode,
nextFocusNode: controller.focusManager.mailboxFieldFocusNode,
), ),
_buildFilterField( _buildFilterField(
textEditingController: controller.mailBoxFilterInputController, textEditingController: controller.mailBoxFilterInputController,
context: context, context: context,
advancedSearchFilterField: AdvancedSearchFilterField.mailBox, advancedSearchFilterField: AdvancedSearchFilterField.mailBox,
isSelectFormList: true, isSelectFormList: true,
currentFocusNode: controller.focusManager.mailboxFieldFocusNode,
nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode,
mouseCursor: SystemMouseCursors.click, mouseCursor: SystemMouseCursors.click,
onTap: () async { onTap: () async {
await controller.selectedMailBox(); await controller.selectedMailBox();
@@ -73,7 +86,7 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
); );
}, },
), ),
const AdvancedSearchFilterFormBottomView() AdvancedSearchFilterFormBottomView(focusManager: controller.focusManager)
], ],
), ),
); );
@@ -120,6 +133,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
VoidCallback? onTap, VoidCallback? onTap,
bool isSelectFormList = false, bool isSelectFormList = false,
MouseCursor? mouseCursor, MouseCursor? mouseCursor,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
}) { }) {
final child = [ final child = [
SizedBox( SizedBox(
@@ -140,6 +155,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
onTap: onTap, onTap: onTap,
context: context, context: context,
mouseCursor: mouseCursor, mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
advancedSearchFilterField: advancedSearchFilterField, advancedSearchFilterField: advancedSearchFilterField,
textEditingController: textEditingController, textEditingController: textEditingController,
) )
@@ -152,6 +169,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
onTap: onTap, onTap: onTap,
context: context, context: context,
mouseCursor: mouseCursor, mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
advancedSearchFilterField: advancedSearchFilterField, advancedSearchFilterField: advancedSearchFilterField,
textEditingController: textEditingController, textEditingController: textEditingController,
) )
@@ -162,6 +181,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
onTap: onTap, onTap: onTap,
context: context, context: context,
mouseCursor: mouseCursor, mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
advancedSearchFilterField: advancedSearchFilterField, advancedSearchFilterField: advancedSearchFilterField,
textEditingController: textEditingController, textEditingController: textEditingController,
), ),
@@ -187,6 +208,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
VoidCallback? onTap, VoidCallback? onTap,
bool isSelectFormList = false, bool isSelectFormList = false,
MouseCursor? mouseCursor, MouseCursor? mouseCursor,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
}) { }) {
switch (advancedSearchFilterField) { switch (advancedSearchFilterField) {
case AdvancedSearchFilterField.date: case AdvancedSearchFilterField.date:
@@ -197,6 +220,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
onTap: onTap, onTap: onTap,
context: context, context: context,
mouseCursor: mouseCursor, mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
advancedSearchFilterField: advancedSearchFilterField, advancedSearchFilterField: advancedSearchFilterField,
textEditingController: textEditingController, textEditingController: textEditingController,
); );
@@ -208,6 +233,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
required BuildContext context, required BuildContext context,
required Set<String> listTagSelected, required Set<String> listTagSelected,
required Set<String> listTagInitial, required Set<String> listTagInitial,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
}) { }) {
final child = [ final child = [
SizedBox( SizedBox(
@@ -229,6 +256,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
}, },
advancedSearchFilterField: advancedSearchFilterField, advancedSearchFilterField: advancedSearchFilterField,
initialTags: listTagInitial, initialTags: listTagInitial,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
onAddTag: (value) { onAddTag: (value) {
if (advancedSearchFilterField == AdvancedSearchFilterField.form) { if (advancedSearchFilterField == AdvancedSearchFilterField.form) {
controller.searchEmailFilter.from.add(value.trim()); controller.searchEmailFilter.from.add(value.trim());
@@ -263,6 +292,8 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
}, },
advancedSearchFilterField: advancedSearchFilterField, advancedSearchFilterField: advancedSearchFilterField,
initialTags: listTagInitial, initialTags: listTagInitial,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
onAddTag: (value) { onAddTag: (value) {
if (advancedSearchFilterField == AdvancedSearchFilterField.form) { if (advancedSearchFilterField == AdvancedSearchFilterField.form) {
controller.searchEmailFilter.from.add(value.trim()); controller.searchEmailFilter.from.add(value.trim());
@@ -312,46 +343,60 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
VoidCallback? onTap, VoidCallback? onTap,
bool isSelectFormList = false, bool isSelectFormList = false,
MouseCursor? mouseCursor, MouseCursor? mouseCursor,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
}) { }) {
return TextField( return RawKeyboardListener(
controller: textEditingController, focusNode: currentFocusNode ?? FocusNode(),
readOnly: isSelectFormList, onKey: (event) {
mouseCursor: mouseCursor, log('AdvancedSearchInputForm::_buildTextField(): Event runtimeType is ${event.runtimeType}');
onTap: onTap, if (event is RawKeyDownEvent &&
decoration: InputDecoration( event.logicalKey == LogicalKeyboardKey.tab) {
filled: true, log('AdvancedSearchInputForm::_buildTextField(): PRESS TAB');
fillColor: AppColor.loginTextFieldBackgroundColor, nextFocusNode?.requestFocus();
contentPadding: const EdgeInsets.only( }
right: 8, },
left: 12, child: TextField(
), controller: textEditingController,
enabledBorder: const OutlineInputBorder( readOnly: isSelectFormList,
borderRadius: BorderRadius.all( mouseCursor: mouseCursor,
Radius.circular(10), textInputAction: TextInputAction.next,
onTap: onTap,
decoration: InputDecoration(
filled: true,
fillColor: AppColor.loginTextFieldBackgroundColor,
contentPadding: const EdgeInsets.only(
right: 8,
left: 12,
), ),
borderSide: BorderSide( enabledBorder: const OutlineInputBorder(
width: 0.5, borderRadius: BorderRadius.all(
color: AppColor.colorInputBorderCreateMailbox, Radius.circular(10),
),
borderSide: BorderSide(
width: 0.5,
color: AppColor.colorInputBorderCreateMailbox,
),
), ),
), border: const OutlineInputBorder(
border: const OutlineInputBorder( borderRadius: BorderRadius.all(
borderRadius: BorderRadius.all( Radius.circular(10),
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,
), ),
); );
} }
@@ -1,40 +1,50 @@
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.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/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/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class AdvancedSearchFilterFormBottomView class AdvancedSearchFilterFormBottomView extends GetWidget<AdvancedFilterController> {
extends GetWidget<AdvancedFilterController> {
final InputFieldFocusManager? focusManager;
const AdvancedSearchFilterFormBottomView({ const AdvancedSearchFilterFormBottomView({
Key? key, Key? key,
this.focusManager,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ResponsiveUtils _responsiveUtils = Get.find<ResponsiveUtils>(); final _responsiveUtils = Get.find<ResponsiveUtils>();
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: _responsiveUtils.isMobile(context) || top: _isMobileAndLandscapeTablet(context, _responsiveUtils) ? 8 : 20),
_responsiveUtils.landscapeTabletSupported(context) ? 8 : 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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) const SizedBox(height: 24)
], ],
Row( Row(
mainAxisAlignment: _responsiveUtils.isMobile(context) || _responsiveUtils.landscapeTabletSupported(context) mainAxisAlignment: _isMobileAndLandscapeTablet(context, _responsiveUtils)
? MainAxisAlignment.spaceEvenly ? MainAxisAlignment.spaceEvenly
: MainAxisAlignment.center, : MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (!_responsiveUtils.isMobile(context) && !_responsiveUtils.landscapeTabletSupported(context)) if (!_isMobileAndLandscapeTablet(context, _responsiveUtils))
Expanded(child: _buildCheckboxHasAttachment(context)), Expanded(child: _buildCheckboxHasAttachment(
context,
currentFocusNode: focusManager?.attachmentCheckboxFocusNode,
nextFocusNode: focusManager?.searchButtonFocusNode)),
..._buildListButton(context, _responsiveUtils), ..._buildListButton(context, _responsiveUtils),
], ],
), ),
@@ -44,8 +54,10 @@ class AdvancedSearchFilterFormBottomView
} }
List<Widget> _buildListButton( List<Widget> _buildListButton(
BuildContext context, ResponsiveUtils responsiveUtils) { BuildContext context,
if (responsiveUtils.isMobile(context) || responsiveUtils.landscapeTabletSupported(context)) { ResponsiveUtils responsiveUtils
) {
if (_isMobileAndLandscapeTablet(context, responsiveUtils)) {
return [ return [
Expanded( Expanded(
child: _buildButton( child: _buildButton(
@@ -72,6 +84,8 @@ class AdvancedSearchFilterFormBottomView
text: AppLocalizations.of(context).search, text: AppLocalizations.of(context).search,
context: context, context: context,
responsiveUtils: responsiveUtils, responsiveUtils: responsiveUtils,
currentFocusNode: focusManager?.searchButtonFocusNode,
nextFocusNode: focusManager?.fromFieldFocusNode
), ),
), ),
]; ];
@@ -99,23 +113,43 @@ class AdvancedSearchFilterFormBottomView
text: AppLocalizations.of(context).search, text: AppLocalizations.of(context).search,
context: context, context: context,
responsiveUtils: responsiveUtils, responsiveUtils: responsiveUtils,
currentFocusNode: focusManager?.searchButtonFocusNode,
nextFocusNode: focusManager?.fromFieldFocusNode
), ),
]; ];
} }
} }
Widget _buildCheckboxHasAttachment(BuildContext context) { Widget _buildCheckboxHasAttachment(
BuildContext context,
{
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
}
) {
return Obx( return Obx(
() => SizedBox( () => SizedBox(
width: 220, width: 220,
child: CheckboxListTile( child: RawKeyboardListener(
contentPadding: EdgeInsets.zero, focusNode: FocusNode(),
controlAffinity: ListTileControlAffinity.leading, onKey: (event) {
value: controller.hasAttachment.value, log('AdvancedSearchFilterFormBottomView::_buildCheckboxHasAttachment(): Event runtimeType is ${event.runtimeType}');
onChanged: (value) { if (event is RawKeyDownEvent &&
controller.hasAttachment.value = value ?? false; 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 VoidCallback onAction,
required BuildContext context, required BuildContext context,
required ResponsiveUtils responsiveUtils, required ResponsiveUtils responsiveUtils,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
}) { }) {
return InkWell( return RawKeyboardListener(
onTap: onAction, focusNode: FocusNode(),
child: Container( onKey: (event) {
height: 44, log('AdvancedSearchFilterFormBottomView::_buildButton(): Event runtimeType is ${event.runtimeType}');
padding: EdgeInsets.symmetric( if (event is RawKeyDownEvent &&
horizontal: responsiveUtils.isMobile(context) || responsiveUtils.landscapeTabletSupported(context) event.logicalKey == LogicalKeyboardKey.tab) {
? 0 : 26), log('AdvancedSearchFilterFormBottomView::_buildButton(): PRESS TAB');
constraints: BoxConstraints( nextFocusNode?.requestFocus();
maxWidth: responsiveUtils.isMobile(context) || responsiveUtils.landscapeTabletSupported(context) }
? double.infinity : 144), },
alignment: Alignment.center, child: buildTextButton(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: colorButton),
child: Text(
text, 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);
}
} }
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.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.onChange,
required this.onDeleteTag, required this.onDeleteTag,
required this.onAddTag, required this.onAddTag,
this.currentFocusNode,
this.nextFocusNode,
}) : super(key: key); }) : super(key: key);
final AdvancedSearchFilterField advancedSearchFilterField; final AdvancedSearchFilterField advancedSearchFilterField;
final Set<String> initialTags; final Set<String> initialTags;
@@ -27,6 +30,8 @@ class TextFieldAutoCompleteEmailAddress extends StatefulWidget {
final Function(String) onChange; final Function(String) onChange;
final Function(String) onDeleteTag; final Function(String) onDeleteTag;
final Function(String) onAddTag; final Function(String) onAddTag;
final FocusNode? currentFocusNode;
final FocusNode? nextFocusNode;
@override @override
State<TextFieldAutoCompleteEmailAddress> createState() => State<TextFieldAutoCompleteEmailAddress> createState() =>
@@ -63,7 +68,7 @@ class _TextFieldAutoCompleteEmailAddressState
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
top: BuildUtils.isWeb ? 5 : 8, top: BuildUtils.isWeb ? 5 : 8,
bottom: 16), bottom: 16),
height: maxHeightSuggestionBox, height: _getHeightSuggestionBox(listEmailAddress.length, 65),
width: maxWidthSuggestionBox, width: maxWidthSuggestionBox,
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Card( child: Card(
@@ -73,12 +78,13 @@ class _TextFieldAutoCompleteEmailAddressState
borderRadius: BorderRadius.circular(20.0), borderRadius: BorderRadius.circular(20.0),
child: Container( child: Container(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
height: maxHeightSuggestionBox, height: _getHeightSuggestionBox(listEmailAddress.length, 65),
width: maxWidthSuggestionBox, width: maxWidthSuggestionBox,
color: Colors.white, color: Colors.white,
child: ListView.builder( child: ListView.builder(
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemExtent: 65,
itemCount: listEmailAddress.length, itemCount: listEmailAddress.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final emailAddress = listEmailAddress.elementAt(index); final emailAddress = listEmailAddress.elementAt(index);
@@ -94,7 +100,10 @@ class _TextFieldAutoCompleteEmailAddressState
); );
}, },
optionsBuilder: (TextEditingValue textEditingValue) { optionsBuilder: (TextEditingValue textEditingValue) {
return widget.optionsBuilder.call(textEditingValue.text); if (textEditingValue.text == '') {
return const Iterable<EmailAddress>.empty();
}
return widget.optionsBuilder.call(textEditingValue.text.toLowerCase());
}, },
onSelected: (EmailAddress selectedTag) { onSelected: (EmailAddress selectedTag) {
_controller.addTag = selectedTag.asString(); _controller.addTag = selectedTag.asString();
@@ -115,53 +124,65 @@ class _TextFieldAutoCompleteEmailAddressState
}, },
inputfieldBuilder: (context, tec, fn, error, onChanged, onSubmitted) { inputfieldBuilder: (context, tec, fn, error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelete) { return ((context, sc, tags, onTagDelete) {
return TextField( return RawKeyboardListener(
controller: tec, focusNode: widget.currentFocusNode ?? FocusNode(),
focusNode: fn, onKey: (event) {
decoration: InputDecoration( log('_TextFieldAutoCompleteEmailAddressState::inputfieldBuilder(): Event runtimeType is ${event.runtimeType}');
filled: true, if (event is RawKeyDownEvent &&
fillColor: AppColor.loginTextFieldBackgroundColor, event.logicalKey == LogicalKeyboardKey.tab) {
contentPadding: const EdgeInsets.only( log('_TextFieldAutoCompleteEmailAddressState::inputfieldBuilder(): PRESS TAB');
right: 8, widget.nextFocusNode?.requestFocus();
left: 12, }
), },
enabledBorder: const OutlineInputBorder( child: TextField(
borderRadius: BorderRadius.all( controller: tec,
Radius.circular(10), focusNode: fn,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
filled: true,
fillColor: AppColor.loginTextFieldBackgroundColor,
contentPadding: const EdgeInsets.only(
right: 8,
left: 12,
), ),
borderSide: BorderSide( enabledBorder: const OutlineInputBorder(
width: 0.5, borderRadius: BorderRadius.all(
color: AppColor.colorInputBorderCreateMailbox, Radius.circular(10),
),
borderSide: BorderSide(
width: 0.5,
color: AppColor.colorInputBorderCreateMailbox,
),
), ),
), border: const OutlineInputBorder(
border: const OutlineInputBorder( borderRadius: BorderRadius.all(
borderRadius: BorderRadius.all( Radius.circular(10),
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), onChanged: (value) {
hintStyle: const TextStyle( onChanged?.call(value);
fontSize: 14, },
color: AppColor.colorHintSearchBar, onSubmitted: (tag) {
), onSubmitted?.call(tag);
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);
},
); );
}); });
}, },
@@ -177,7 +198,7 @@ class _TextFieldAutoCompleteEmailAddressState
return Container( return Container(
color: Colors.white, color: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
child: Row(children: [ child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Container( Container(
width: 40, width: 40,
height: 40, height: 40,
@@ -200,6 +221,7 @@ class _TextFieldAutoCompleteEmailAddressState
padding: const EdgeInsets.only(left: 10), padding: const EdgeInsets.only(left: 10),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
emailAddress.asString(), emailAddress.asString(),
@@ -272,10 +294,8 @@ class _TextFieldAutoCompleteEmailAddressState
fontSize: 17, fontSize: 17,
fontWeight: FontWeight.normal)), fontWeight: FontWeight.normal)),
const SizedBox(width: 4), const SizedBox(width: 4),
GestureDetector( InkWell(
onTap: () { onTap: () => onTagDelete.call(tag),
onTagDelete.call(tag);
},
child: SvgPicture.asset( child: SvgPicture.asset(
_imagePaths.icClose, _imagePaths.icClose,
width: 28, width: 28,
@@ -300,14 +320,16 @@ class _TextFieldAutoCompleteEmailAddressState
return 0; return 0;
} }
double get maxHeightSuggestionBox { double _getHeightSuggestionBox(int countItem, double heightItem) {
final maxHeightList = countItem * heightItem;
if (BuildUtils.isWeb) { if (BuildUtils.isWeb) {
return 250; return maxHeightList > 250 ? 250 : maxHeightList;
} else { } else {
if (_responsiveUtils.isLandscapeMobile(context)) { if (_responsiveUtils.isLandscapeMobile(context)) {
return 180; return maxHeightList > 180 ? 180 : maxHeightList;
} else { } else {
return 250; return maxHeightList > 250 ? 250 : maxHeightList;
} }
} }
} }