TF-691 Support change focus input field by TAB key on web keyboard
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+81
-36
@@ -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,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-37
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-54
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user