Reuse widgets for advanced search form view

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-11 02:48:26 +07:00
committed by Dat H. Pham
parent 42339a3003
commit 58a8d93ed3
14 changed files with 292 additions and 873 deletions
@@ -0,0 +1,55 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class DefaultButtonArrowDownFieldWidget extends StatelessWidget {
final String text;
final String iconArrowDown;
final VoidCallback onTap;
const DefaultButtonArrowDownFieldWidget({
super.key,
required this.text,
required this.iconArrowDown,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onTap,
borderRadius: const BorderRadius.all(Radius.circular(10)),
focusColor: AppColor.colorMailboxHovered,
child: Container(
height: 40,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(10)),
border: Border.all(
color: AppColor.m3Neutral90,
width: 1,
),
),
padding: const EdgeInsetsDirectional.only(start: 12, end: 8),
child: Row(
children: [
Expanded(
child: Text(
text,
style: ThemeUtils.textStyleBodyBody3(
color: AppColor.m3SurfaceBackground,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
SvgPicture.asset(iconArrowDown),
],
),
),
),
);
}
}
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_button_arrow_down_field_widget.dart';
class DefaultButtonArrowDownFieldWithTabKeyWidget extends StatelessWidget {
final String text;
final String iconArrowDown;
final VoidCallback onTap;
final FocusNode currentFocusNode;
final FocusNode nextFocusNode;
const DefaultButtonArrowDownFieldWithTabKeyWidget({
super.key,
required this.text,
required this.iconArrowDown,
required this.onTap,
required this.currentFocusNode,
required this.nextFocusNode,
});
@override
Widget build(BuildContext context) {
return KeyboardListener(
focusNode: currentFocusNode,
onKeyEvent: _onKeyEvent,
child: DefaultButtonArrowDownFieldWidget(
text: text,
iconArrowDown: iconArrowDown,
onTap: onTap,
),
);
}
void _onKeyEvent(KeyEvent event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.tab) {
nextFocusNode.requestFocus();
}
}
}
@@ -3,7 +3,7 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/date_drop_down_button.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_date_drop_down_button.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnOpenDatPicker = void Function();
@@ -8,7 +8,7 @@ typedef OnTextChange = void Function(String text);
class DefaultInputFieldWithTabKeyWidget extends StatelessWidget {
final TextEditingController textEditingController;
final FocusNode currentFocusNode;
final FocusNode? nextFocusNode;
final FocusNode nextFocusNode;
final String? hintText;
final OnTextChange? onTextChange;
final OnTextSubmitted? onTextSubmitted;
@@ -18,7 +18,7 @@ class DefaultInputFieldWithTabKeyWidget extends StatelessWidget {
super.key,
required this.textEditingController,
required this.currentFocusNode,
this.nextFocusNode,
required this.nextFocusNode,
this.hintText,
this.onTextChange,
this.onTextSubmitted,
@@ -29,7 +29,7 @@ class DefaultInputFieldWithTabKeyWidget extends StatelessWidget {
Widget build(BuildContext context) {
final bodyWidget = KeyboardListener(
focusNode: currentFocusNode,
onKeyEvent: nextFocusNode == null ? null : _onKeyEvent,
onKeyEvent: _onKeyEvent,
child: DefaultInputFieldWidget(
textEditingController: textEditingController,
hintText: hintText,
@@ -48,7 +48,7 @@ class DefaultInputFieldWithTabKeyWidget extends StatelessWidget {
void _onKeyEvent(KeyEvent event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.tab) {
nextFocusNode?.requestFocus();
nextFocusNode.requestFocus();
}
}
}
@@ -37,6 +37,7 @@ class AdvancedFilterController extends BaseController {
final startDate = Rxn<DateTime>();
final endDate = Rxn<DateTime>();
final sortOrderType = EmailSortOrderType.mostRecent.obs;
final selectedFolderName = Rxn<String>();
final GlobalKey<TagsEditorState> keyFromEmailTagEditor = GlobalKey<TagsEditorState>();
final GlobalKey<TagsEditorState> keyToEmailTagEditor = GlobalKey<TagsEditorState>();
@@ -51,7 +52,6 @@ class AdvancedFilterController extends BaseController {
TextEditingController subjectFilterInputController = TextEditingController();
TextEditingController hasKeyWordFilterInputController = TextEditingController();
TextEditingController notKeyWordFilterInputController = TextEditingController();
TextEditingController mailBoxFilterInputController = TextEditingController();
final search.SearchController searchController = Get.find<search.SearchController>();
final MailboxDashBoardController _mailboxDashBoardController = Get.find<MailboxDashBoardController>();
@@ -206,7 +206,7 @@ class AdvancedFilterController extends BaseController {
final mailboxName = context.mounted
? _destinationMailboxSelected?.getDisplayName(context)
: _destinationMailboxSelected?.name?.name;
mailBoxFilterInputController.text = StringConvert.writeNullToEmpty(mailboxName);
selectedFolderName.value = StringConvert.writeNullToEmpty(mailboxName);
_updateMemorySearchFilter(mailboxOption: optionOf(_destinationMailboxSelected));
}
@@ -276,7 +276,7 @@ class AdvancedFilterController extends BaseController {
}
if (context != null) {
mailBoxFilterInputController.text = _memorySearchFilter.mailbox == null
selectedFolderName.value = _memorySearchFilter.mailbox == null
? AppLocalizations.of(context).allFolders
: StringConvert.writeNullToEmpty(
_memorySearchFilter.mailbox?.getDisplayName(context));
@@ -450,6 +450,7 @@ class AdvancedFilterController extends BaseController {
endDate.value = null;
receiveTimeType.value = EmailReceiveTimeType.allTime;
hasAttachment.value = false;
selectedFolderName.value = null;
listFromEmailAddress.clear();
listToEmailAddress.clear();
_destinationMailboxSelected = null;
@@ -459,7 +460,6 @@ class AdvancedFilterController extends BaseController {
subjectFilterInputController.clear();
hasKeyWordFilterInputController.clear();
notKeyWordFilterInputController.clear();
mailBoxFilterInputController.clear();
fromEmailAddressController.clear();
toEmailAddressController.clear();
}
@@ -600,7 +600,6 @@ class AdvancedFilterController extends BaseController {
subjectFilterInputController.dispose();
hasKeyWordFilterInputController.dispose();
notKeyWordFilterInputController.dispose();
mailBoxFilterInputController.dispose();
toEmailAddressController.dispose();
fromEmailAddressController.dispose();
_unregisterWorkerListener();
@@ -10,6 +10,8 @@ class AdvancedSearchInputFormStyle {
static const double inputFieldBorderRadius = 10;
static const double inputFieldBorderWidth = 1;
static const double inputFieldHeight = 40;
static const double labelMaxWidth = 112;
static const double labelSpace = 12;
static const Color inputFieldBorderColor = AppColor.m3Neutral90;
static const Color inputFieldBackgroundColor = Colors.white;
@@ -1,26 +1,41 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class SortByDropdownStyle {
static const int dropdownElevation = 4;
static const int dropdownElevation = 0;
static const double height = 44;
static const double dropdownMaxHeight = 332;
static const double scrollbarThickness = 6;
static const double checkedIconSize = 20;
static const Offset dropdownOffset = Offset(0.0, -8.0);
static const Offset dropdownOffset = Offset(0.0, -3.0);
static const BoxDecoration dropdownDecoration = BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: Colors.white
static BoxDecoration dropdownDecoration = BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
color: Colors.white,
border: Border.all(color: AppColor.m3Tertiary60),
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 24)],
);
static const EdgeInsetsDirectional buttonPadding = EdgeInsetsDirectional.only(start: 12, end: 8);
static const EdgeInsetsGeometry buttonPadding = EdgeInsetsDirectional.only(
start: 12,
end: 8,
);
static const EdgeInsetsGeometry dropdownPadding = EdgeInsets.all(12);
static const MenuItemStyleData menuItemStyleData = MenuItemStyleData(
height: height,
padding: EdgeInsets.symmetric(horizontal: 12),
);
static const Radius dropdownScrollbarRadius = Radius.circular(40);
static TextStyle menuItemStyle = ThemeUtils.textStyleInter400.copyWith(
fontSize: 15,
height: 20 / 15,
letterSpacing: -0.15,
color: Colors.black,
);
}
@@ -1,45 +0,0 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/advanced_search_input_form_style.dart';
class TextFieldAutoCompleteEmailAddressWebStyles {
static const EdgeInsetsGeometry textInputContentPadding =
EdgeInsetsDirectional.only(
top: 14,
bottom: 14,
start: 12,
end: 8,
);
static const EdgeInsetsGeometry textInputContentPaddingWithSomeTag =
EdgeInsetsDirectional.symmetric(vertical: 14);
static const EdgeInsets tagEditorPadding = EdgeInsets.symmetric(horizontal: 12);
static const double suggestionBoxRadius = 20.0;
static const double minTextFieldWidth = 40.0;
static const double suggestionBoxElevation = 20.0;
static const double suggestionBoxMaxHeight = 350.0;
static const double suggestionBoxItemHeight = ComposerStyle.suggestionItemHeight;
static const double fieldTitleWidth = 112.0;
static const double space = 12.0;
static const InputBorder textInputBorder = OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius)),
borderSide: BorderSide.none,
);
static const Color textInputFillColor = Colors.white;
static const Color focusedBorderColor = AppColor.primaryColor;
static const Color suggestionBoxBackgroundColor = Colors.white;
static const Color cursorColor = AppColor.primaryColor;
static TextStyle textInputHintStyle = ThemeUtils.textStyleBodyBody3(
color: AppColor.m3Tertiary,
);
static const Duration debounceDuration = Duration(milliseconds: 150);
}
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/base/model/filter_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/advanced_search_input_form_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/label_advanced_search_field_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class AdvancedSearchFieldWidget extends StatelessWidget {
final FilterField filterField;
final Widget child;
const AdvancedSearchFieldWidget({
super.key,
required this.filterField,
required this.child,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: AdvancedSearchInputFormStyle.inputFieldHeight,
child: Row(
children: [
SizedBox(
width: AdvancedSearchInputFormStyle.labelMaxWidth,
child: LabelAdvancedSearchFieldWidget(
name: filterField.getTitle(AppLocalizations.of(context)),
),
),
const SizedBox(width: AdvancedSearchInputFormStyle.labelSpace),
Expanded(child: child),
],
),
);
}
}
@@ -1,157 +1,146 @@
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';
import 'package:tmail_ui_user/features/base/widget/popup_item_no_icon_widget.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_autocomplete_input_field_widget.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_button_arrow_down_field_with_tab_key_widget.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_date_drop_down_field_widget.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart';
import 'package:tmail_ui_user/features/base/model/filter_filter.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/styles/advanced_search_input_form_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_field_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/date_drop_down_button.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/label_advanced_search_field_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_autocomplete_email_address_web.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
with PopupContextMenuActionMixin {
AdvancedSearchInputForm({
Key? key,
}) : super(key: key);
class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController> {
const AdvancedSearchInputForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context);
return FocusTraversalGroup(
child: Column(
children: [
Obx(() => TextFieldAutocompleteEmailAddressWeb(
field: FilterField.from,
listEmailAddress: controller.listFromEmailAddress,
expandMode: controller.fromAddressExpandMode.value,
minInputLengthAutocomplete: controller
.mailboxDashBoardController
.minInputLengthAutocomplete,
controller: controller.fromEmailAddressController,
focusNode: controller.focusManager.fromFieldFocusNode,
nextFocusNode: controller.focusManager.toFieldFocusNode,
keyTagEditor: controller.keyFromEmailTagEditor,
onShowFullListEmailAddressAction: controller.showFullEmailAddress,
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
onSearchAction: controller.onSearchAction,
onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress,
)),
AdvancedSearchFieldWidget(
filterField: FilterField.from,
child: Obx(() => DefaultAutocompleteInputFieldWidget(
field: FilterField.from,
listEmailAddress: controller.listFromEmailAddress,
expandMode: controller.fromAddressExpandMode.value,
minInputLengthAutocomplete: controller
.mailboxDashBoardController
.minInputLengthAutocomplete,
controller: controller.fromEmailAddressController,
focusNode: controller.focusManager.fromFieldFocusNode,
nextFocusNode: controller.focusManager.toFieldFocusNode,
keyTagEditor: controller.keyFromEmailTagEditor,
onShowFullListEmailAddressAction: controller.showFullEmailAddress,
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
onSearchAction: controller.onSearchAction,
onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress,
)),
),
const SizedBox(height: 12),
Obx(() => TextFieldAutocompleteEmailAddressWeb(
field: FilterField.to,
listEmailAddress: controller.listToEmailAddress,
expandMode: controller.toAddressExpandMode.value,
minInputLengthAutocomplete: controller
.mailboxDashBoardController
.minInputLengthAutocomplete,
controller: controller.toEmailAddressController,
focusNode: controller.focusManager.toFieldFocusNode,
nextFocusNode: controller.focusManager.subjectFieldFocusNode,
keyTagEditor: controller.keyToEmailTagEditor,
onShowFullListEmailAddressAction: controller.showFullEmailAddress,
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
onSearchAction: controller.onSearchAction,
onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress,
)),
AdvancedSearchFieldWidget(
filterField: FilterField.to,
child: Obx(() => DefaultAutocompleteInputFieldWidget(
field: FilterField.to,
listEmailAddress: controller.listToEmailAddress,
expandMode: controller.toAddressExpandMode.value,
minInputLengthAutocomplete: controller
.mailboxDashBoardController
.minInputLengthAutocomplete,
controller: controller.toEmailAddressController,
focusNode: controller.focusManager.toFieldFocusNode,
nextFocusNode: controller.focusManager.subjectFieldFocusNode,
keyTagEditor: controller.keyToEmailTagEditor,
onShowFullListEmailAddressAction: controller.showFullEmailAddress,
onUpdateListEmailAddressAction: controller.updateListEmailAddress,
onSuggestionEmailAddress: controller.getAutoCompleteSuggestion,
onSearchAction: controller.onSearchAction,
onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress,
)),
),
const SizedBox(height: 12),
_buildFilterField(
textEditingController: controller.subjectFilterInputController,
context: context,
AdvancedSearchFieldWidget(
filterField: FilterField.subject,
currentFocusNode: controller.focusManager.subjectFieldFocusNode,
nextFocusNode: controller.focusManager.hasKeywordFieldFocusNode,
onTextChange: (value) => controller.onTextChanged(
FilterField.subject,
value
child: DefaultInputFieldWithTabKeyWidget(
textEditingController: controller.subjectFilterInputController,
currentFocusNode: controller.focusManager.subjectFieldFocusNode,
nextFocusNode: controller.focusManager.hasKeywordFieldFocusNode,
hintText: FilterField.subject.getHintText(appLocalizations),
onTextChange: (value) => controller.onTextChanged(
FilterField.subject,
value,
),
),
),
const SizedBox(height: 12),
_buildFilterField(
textEditingController: controller.hasKeyWordFilterInputController,
context: context,
AdvancedSearchFieldWidget(
filterField: FilterField.hasKeyword,
currentFocusNode: controller.focusManager.hasKeywordFieldFocusNode,
nextFocusNode: controller.focusManager.notKeywordFieldFocusNode,
onTextChange: (value) => controller.onTextChanged(
FilterField.hasKeyword,
value
child: DefaultInputFieldWithTabKeyWidget(
textEditingController: controller.hasKeyWordFilterInputController,
currentFocusNode: controller.focusManager.hasKeywordFieldFocusNode,
nextFocusNode: controller.focusManager.notKeywordFieldFocusNode,
hintText: FilterField.hasKeyword.getHintText(appLocalizations),
onTextChange: (value) => controller.onTextChanged(
FilterField.hasKeyword,
value,
),
),
),
const SizedBox(height: 12),
_buildFilterField(
textEditingController: controller.notKeyWordFilterInputController,
context: context,
AdvancedSearchFieldWidget(
filterField: FilterField.notKeyword,
currentFocusNode: controller.focusManager.notKeywordFieldFocusNode,
nextFocusNode: controller.focusManager.mailboxFieldFocusNode,
onTextChange: (value) => controller.onTextChanged(
FilterField.notKeyword,
value
child: DefaultInputFieldWithTabKeyWidget(
textEditingController: controller.notKeyWordFilterInputController,
currentFocusNode: controller.focusManager.notKeywordFieldFocusNode,
nextFocusNode: controller.focusManager.mailboxFieldFocusNode,
hintText: FilterField.notKeyword.getHintText(appLocalizations),
onTextChange: (value) => controller.onTextChanged(
FilterField.notKeyword,
value,
),
),
),
const SizedBox(height: 12),
_buildFilterField(
textEditingController: controller.mailBoxFilterInputController,
context: context,
AdvancedSearchFieldWidget(
filterField: FilterField.mailBox,
isSelectFormList: true,
currentFocusNode: controller.focusManager.mailboxFieldFocusNode,
nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode,
mouseCursor: SystemMouseCursors.click,
onTap: () => controller.selectedMailBox(context)
child: Obx(() => DefaultButtonArrowDownFieldWithTabKeyWidget(
text: controller.selectedFolderName.value ??
AppLocalizations.of(context).allFolders,
iconArrowDown: controller.imagePaths.icDropDown,
currentFocusNode: controller.focusManager.mailboxFieldFocusNode,
nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode,
onTap: () => controller.selectedMailBox(context),
)),
),
const SizedBox(height: 12),
Row(children: [
Expanded(child: _buildFilterField(
context: context,
filterField: FilterField.date,
isSelectFormList: true,
onTap: () {
openContextMenuAction(
context,
_buildEmailReceiveTimeTypeActionTiles(context),
);
},
)),
const SizedBox(width: 8),
Container(
decoration: BoxDecoration(
border: Border.all(
color: AdvancedSearchInputFormStyle.inputFieldBorderColor,
width: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
),
borderRadius: const BorderRadius.all(
Radius.circular(
AdvancedSearchInputFormStyle.inputFieldBorderRadius,
AdvancedSearchFieldWidget(
filterField: FilterField.date,
child: Obx(() => DefaultDateDropDownFieldWidget(
imagePaths: controller.imagePaths,
receiveTimeTypes: EmailReceiveTimeType.valuesForSearch,
startDate: controller.startDate.value,
endDate: controller.endDate.value,
receiveTimeTypeSelected: controller.receiveTimeType.value,
onReceiveTimeSelected: (receiveTime) =>
controller.updateReceiveDateSearchFilter(
context,
receiveTime,
),
),
),
height: AdvancedSearchInputFormStyle.inputFieldHeight,
width: AdvancedSearchInputFormStyle.inputFieldHeight,
child: TMailButtonWidget.fromIcon(
icon: controller.imagePaths.icCalendarSB,
iconSize: 22,
iconColor: AppColor.steelGray400,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).selectDate,
onTapActionCallback: () => controller.selectDateRange(context),
),
),
]),
onOpenDatPicker: () => controller.selectDateRange(context),
)),
),
const SizedBox(height: 12),
_buildFilterField(
context: context,
filterField: FilterField.sortBy
AdvancedSearchFieldWidget(
filterField: FilterField.sortBy,
child: Obx(() => SortByDropDownButton(
imagePaths: controller.imagePaths,
sortOrderSelected: controller.sortOrderType.value,
onSortOrderSelected: controller.updateSortOrder,
)),
),
const SizedBox(height: 24),
AdvancedSearchFilterFormBottomView(focusManager: controller.focusManager)
@@ -159,243 +148,4 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
),
);
}
List<Widget> _buildEmailReceiveTimeTypeActionTiles(BuildContext context) {
return EmailReceiveTimeType.valuesForSearch
.map((receiveTime) => PopupMenuItem(
padding: EdgeInsets.zero,
child: PopupItemNoIconWidget(
receiveTime.getTitle(context),
svgIconSelected: controller.imagePaths.icFilterSelected,
maxWidth: 320,
isSelected: controller.receiveTimeType.value == receiveTime,
onCallbackAction: () => controller.updateReceiveDateSearchFilter(context, receiveTime),
)))
.toList();
}
Widget _buildFilterField({
required BuildContext context,
required FilterField filterField,
TextEditingController? textEditingController,
VoidCallback? onTap,
bool isSelectFormList = false,
MouseCursor? mouseCursor,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
ValueChanged<String>? onTextChange,
}) {
final child = [
SizedBox(
width: _isVerticalArrange(context) ? null : 112,
child: LabelAdvancedSearchFieldWidget(
name: filterField.getTitle(AppLocalizations.of(context)),
),
),
if (_isVerticalArrange(context))
const SizedBox(height: 12)
else
const SizedBox(width: 12),
if (controller.responsiveUtils.isMobile(context))
_buildTextField(
isSelectFormList: isSelectFormList,
onTap: onTap,
context: context,
mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
filterField: filterField,
textEditingController: textEditingController,
onTextChange: onTextChange,
)
else if (controller.responsiveUtils.landscapeTabletSupported(context))
if (filterField == FilterField.date)
Obx(() => DateDropDownButton(
imagePaths: controller.imagePaths,
receiveTimeTypes: EmailReceiveTimeType.valuesForSearch,
startDate: controller.startDate.value,
endDate: controller.endDate.value,
receiveTimeTypeSelected: controller.receiveTimeType.value,
onReceiveTimeSelected: (receiveTime) => controller.updateReceiveDateSearchFilter(context, receiveTime),
))
else if (filterField == FilterField.sortBy)
Obx(() => SortByDropDownButton(
imagePaths: controller.imagePaths,
sortOrderSelected: controller.sortOrderType.value,
onSortOrderSelected: controller.updateSortOrder,
))
else
_buildTextField(
isSelectFormList: isSelectFormList,
onTap: onTap,
context: context,
mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
filterField: filterField,
textEditingController: textEditingController,
onTextChange: onTextChange,
)
else
Expanded(
child: _buildTextFieldFilterForWeb(
isSelectFormList: isSelectFormList,
onTap: onTap,
context: context,
mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
filterField: filterField,
textEditingController: textEditingController,
onTextChange: onTextChange,
),
)
];
return _isVerticalArrange(context)
? Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: child,
)
: SizedBox(
height: AdvancedSearchInputFormStyle.inputFieldHeight,
child: Row(children: child),
);
}
bool _isVerticalArrange(BuildContext context) =>
controller.responsiveUtils.isMobile(context) ||
controller.responsiveUtils.landscapeTabletSupported(context);
Widget _buildTextFieldFilterForWeb({
required BuildContext context,
required FilterField filterField,
TextEditingController? textEditingController,
VoidCallback? onTap,
bool isSelectFormList = false,
MouseCursor? mouseCursor,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
ValueChanged<String>? onTextChange,
}) {
switch (filterField) {
case FilterField.date:
return Obx(() => DateDropDownButton(
imagePaths: controller.imagePaths,
receiveTimeTypes: EmailReceiveTimeType.valuesForSearch,
startDate: controller.startDate.value,
endDate: controller.endDate.value,
receiveTimeTypeSelected: controller.receiveTimeType.value,
onReceiveTimeSelected: (receiveTime) => controller.updateReceiveDateSearchFilter(context, receiveTime),
));
case FilterField.sortBy:
return Obx(() => SortByDropDownButton(
imagePaths: controller.imagePaths,
sortOrderSelected: controller.sortOrderType.value,
onSortOrderSelected: controller.updateSortOrder,
));
default:
return _buildTextField(
isSelectFormList: isSelectFormList,
onTap: onTap,
context: context,
mouseCursor: mouseCursor,
currentFocusNode: currentFocusNode,
nextFocusNode: nextFocusNode,
filterField: filterField,
textEditingController: textEditingController,
onTextChange: onTextChange,
);
}
}
Widget _buildTextField({
required BuildContext context,
required FilterField filterField,
TextEditingController? textEditingController,
VoidCallback? onTap,
bool isSelectFormList = false,
MouseCursor? mouseCursor,
FocusNode? currentFocusNode,
FocusNode? nextFocusNode,
ValueChanged<String>? onTextChange,
}) {
return KeyboardListener(
focusNode: currentFocusNode ?? FocusNode(),
onKeyEvent: (event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.tab) {
nextFocusNode?.requestFocus();
}
},
child: TextFieldBuilder(
controller: textEditingController,
readOnly: isSelectFormList,
mouseCursor: mouseCursor,
maxLines: 1,
textInputAction: isSelectFormList ? TextInputAction.done : TextInputAction.next,
textStyle: AdvancedSearchInputFormStyle.inputTextStyle,
onTap: onTap,
onTextSubmitted: (value) {
if (isSelectFormList) {
onTap?.call();
} else {
controller.onSearchAction();
popBack();
}
},
onTextChange: onTextChange,
decoration: InputDecoration(
constraints: const BoxConstraints(
maxHeight: AdvancedSearchInputFormStyle.inputFieldHeight,
),
filled: true,
fillColor: AdvancedSearchInputFormStyle.inputFieldBackgroundColor,
contentPadding: const EdgeInsetsDirectional.only(start: 12, end: 8),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius),
),
borderSide: BorderSide(
width: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
color: AdvancedSearchInputFormStyle.inputFieldBorderColor,
),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius),
),
borderSide: BorderSide(
width: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
color: AdvancedSearchInputFormStyle.inputFieldBorderColor,
),
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius),
),
borderSide: BorderSide(
width: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
color: AppColor.primaryColor,
),
),
hintText: filterField.getHintText(AppLocalizations.of(context)),
hintStyle: ThemeUtils.textStyleBodyBody3(
color: filterField == FilterField.mailBox
? Colors.black
: AppColor.m3Tertiary,
),
suffixIconConstraints: const BoxConstraints(minHeight: 24, minWidth: 24),
suffixIcon: isSelectFormList
? buildIconWeb(
icon: SvgPicture.asset(
controller.imagePaths.icDropDown,
),
)
: null,
),
),
);
}
}
@@ -1,5 +1,4 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -32,30 +31,26 @@ class SortByDropDownButton extends StatelessWidget {
items: EmailSortOrderType.values
.map((sortType) => DropdownMenuItem<EmailSortOrderType>(
value: sortType,
enabled: sortType != sortOrderSelected,
child: PointerInterceptor(
child: Container(
color: Colors.transparent,
height: SortByDropdownStyle.height,
child: Row(
children: [
Expanded(
child: Text(
sortType.getTitle(context),
style: sortType.getTextStyle(isInDropdown: true),
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
),
child: Row(
children: [
Expanded(
child: Text(
sortType.getTitle(context),
style: SortByDropdownStyle.menuItemStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (sortType == sortOrderSelected)
SvgPicture.asset(
imagePaths.icChecked,
width: SortByDropdownStyle.checkedIconSize,
height: SortByDropdownStyle.checkedIconSize,
fit: BoxFit.fill,
)
],
),
),
if (sortType == sortOrderSelected)
SvgPicture.asset(
imagePaths.icChecked,
width: SortByDropdownStyle.checkedIconSize,
height: SortByDropdownStyle.checkedIconSize,
fit: BoxFit.fill,
),
],
)
)
)).toList(),
@@ -80,8 +75,7 @@ class SortByDropDownButton extends StatelessWidget {
sortOrderSelected?.getTitle(context) ?? '',
style: AdvancedSearchInputFormStyle.inputTextStyle,
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
overflow: TextOverflow.ellipsis,
)
),
SvgPicture.asset(imagePaths.icDropDown)
@@ -89,35 +83,19 @@ class SortByDropDownButton extends StatelessWidget {
),
),
onChanged: onSortOrderSelected,
buttonStyleData: ButtonStyleData(
height: AdvancedSearchInputFormStyle.inputFieldHeight,
padding: SortByDropdownStyle.buttonPadding,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius),
),
border: Border.all(
color: AdvancedSearchInputFormStyle.inputFieldBorderColor,
width: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
),
color: AdvancedSearchInputFormStyle.inputFieldBackgroundColor,
)
),
dropdownStyleData: DropdownStyleData(
maxHeight: SortByDropdownStyle.dropdownMaxHeight,
decoration: SortByDropdownStyle.dropdownDecoration,
elevation: SortByDropdownStyle.dropdownElevation,
offset: SortByDropdownStyle.dropdownOffset,
padding: SortByDropdownStyle.dropdownPadding,
scrollbarTheme: ScrollbarThemeData(
radius: SortByDropdownStyle.dropdownScrollbarRadius,
thickness: WidgetStateProperty.all<double>(SortByDropdownStyle.scrollbarThickness),
thumbVisibility: WidgetStateProperty.all<bool>(true),
)
),
iconStyleData: IconStyleData(
icon: SvgPicture.asset(imagePaths.icDropDown),
),
menuItemStyleData: SortByDropdownStyle.menuItemStyleData
menuItemStyleData: SortByDropdownStyle.menuItemStyleData,
)
)
);
@@ -1,409 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/extensions/email_address_extension.dart';
import 'package:model/mailbox/expand_mode.dart';
import 'package:super_tag_editor/tag_editor.dart';
import 'package:tmail_ui_user/features/base/model/filter_filter.dart';
import 'package:tmail_ui_user/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_email_address.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/advanced_search_input_form_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/text_field_autocomplete_email_address_web_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/label_advanced_search_field_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
typedef OnSuggestionEmailAddress = Future<List<EmailAddress>> Function(String word, {int? limit});
typedef OnUpdateListEmailAddressAction = void Function(FilterField field, List<EmailAddress> newData);
typedef OnDeleteEmailAddressTypeAction = void Function(FilterField field);
typedef OnShowFullListEmailAddressAction = void Function(FilterField field);
typedef OnDeleteTagAction = void Function(EmailAddress emailAddress);
class TextFieldAutocompleteEmailAddressWeb extends StatefulWidget {
final FilterField field;
final List<EmailAddress> listEmailAddress;
final ExpandMode expandMode;
final FocusNode? focusNode;
final GlobalKey? keyTagEditor;
final FocusNode? nextFocusNode;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final OnSuggestionEmailAddress? onSuggestionEmailAddress;
final OnDeleteTagAction? onDeleteTag;
final OnUpdateListEmailAddressAction? onUpdateListEmailAddressAction;
final OnDeleteEmailAddressTypeAction? onDeleteEmailAddressTypeAction;
final OnShowFullListEmailAddressAction? onShowFullListEmailAddressAction;
final OnRemoveDraggableEmailAddressAction? onRemoveDraggableEmailAddressAction;
final TextEditingController? controller;
final VoidCallback? onSearchAction;
final int minInputLengthAutocomplete;
const TextFieldAutocompleteEmailAddressWeb({
Key? key,
required this.field,
required this.listEmailAddress,
this.expandMode = ExpandMode.EXPAND,
this.minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete,
this.focusNode,
this.keyTagEditor,
this.nextFocusNode,
this.padding,
this.margin,
this.onSuggestionEmailAddress,
this.onDeleteTag,
this.onUpdateListEmailAddressAction,
this.onDeleteEmailAddressTypeAction,
this.onShowFullListEmailAddressAction,
this.onRemoveDraggableEmailAddressAction,
this.controller,
this.onSearchAction,
}) : super(key: key);
@override
State<TextFieldAutocompleteEmailAddressWeb> createState() => _TextFieldAutocompleteEmailAddressWebState();
}
class _TextFieldAutocompleteEmailAddressWebState extends State<TextFieldAutocompleteEmailAddressWeb> {
final _imagePaths = Get.find<ImagePaths>();
bool _lastTagFocused = false;
bool _isDragging = false;
late List<EmailAddress> _currentListEmailAddress;
Timer? _gapBetweenTagChangedAndFindSuggestion;
@override
void initState() {
super.initState();
_currentListEmailAddress = widget.listEmailAddress;
}
@override
void didUpdateWidget(covariant TextFieldAutocompleteEmailAddressWeb oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.listEmailAddress != oldWidget.listEmailAddress) {
_currentListEmailAddress = widget.listEmailAddress;
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: ((context, constraints) {
return Row(
children: [
SizedBox(
width: TextFieldAutoCompleteEmailAddressWebStyles.fieldTitleWidth,
child: LabelAdvancedSearchFieldWidget(
name: widget.field.getTitle(AppLocalizations.of(context)),
),
),
const SizedBox(width: TextFieldAutoCompleteEmailAddressWebStyles.space),
Expanded(
child: StatefulBuilder(
builder: ((context, setState) {
return DragTarget<DraggableEmailAddress>(
onAcceptWithDetails: (draggableEmailAddress) =>
_handleAcceptDraggableEmailAddressAction(
draggableEmailAddress.data,
setState
),
onLeave: (draggableEmailAddress) {
if (_isDragging) {
setState(() => _isDragging = false);
}
},
onMove: (details) {
if (!_isDragging) {
setState(() => _isDragging = true);
}
},
builder: (_, __, ___) {
return TagEditor<SuggestionEmailAddress>(
key: widget.keyTagEditor,
length: _collapsedListEmailAddress.length,
controller: widget.controller,
focusNodeKeyboard: widget.focusNode,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
cursorColor: TextFieldAutoCompleteEmailAddressWebStyles.cursorColor,
debounceDuration: TextFieldAutoCompleteEmailAddressWebStyles.debounceDuration,
inputDecoration: InputDecoration(
filled: true,
constraints: const BoxConstraints(
maxHeight:
AdvancedSearchInputFormStyle.inputFieldHeight,
),
fillColor: TextFieldAutoCompleteEmailAddressWebStyles.textInputFillColor,
border: TextFieldAutoCompleteEmailAddressWebStyles.textInputBorder,
hintText: widget.field.getHintText(AppLocalizations.of(context)),
hintStyle: TextFieldAutoCompleteEmailAddressWebStyles.textInputHintStyle,
isDense: true,
contentPadding: _currentListEmailAddress.isNotEmpty
? TextFieldAutoCompleteEmailAddressWebStyles.textInputContentPaddingWithSomeTag
: TextFieldAutoCompleteEmailAddressWebStyles.textInputContentPadding
),
padding: _currentListEmailAddress.isNotEmpty
? TextFieldAutoCompleteEmailAddressWebStyles.tagEditorPadding
: EdgeInsets.zero,
borderRadius: AdvancedSearchInputFormStyle.inputFieldBorderRadius,
borderSize: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
focusedBorderColor: TextFieldAutoCompleteEmailAddressWebStyles.focusedBorderColor,
enableBorder: true,
enableBorderColor: _isDragging
? AppColor.primaryColor
: AdvancedSearchInputFormStyle.inputFieldBorderColor,
minTextFieldWidth: TextFieldAutoCompleteEmailAddressWebStyles.minTextFieldWidth,
resetTextOnSubmitted: true,
autoScrollToInput: false,
suggestionsBoxElevation: TextFieldAutoCompleteEmailAddressWebStyles.suggestionBoxElevation,
suggestionsBoxBackgroundColor: TextFieldAutoCompleteEmailAddressWebStyles.suggestionBoxBackgroundColor,
suggestionsBoxRadius: TextFieldAutoCompleteEmailAddressWebStyles.suggestionBoxRadius,
suggestionsBoxMaxHeight: TextFieldAutoCompleteEmailAddressWebStyles.suggestionBoxMaxHeight,
suggestionItemHeight: TextFieldAutoCompleteEmailAddressWebStyles.suggestionBoxItemHeight,
suggestionBoxWidth: _getSuggestionBoxWidth(constraints.maxWidth),
textStyle: AdvancedSearchInputFormStyle.inputTextStyle,
onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, setState),
onDeleteTagAction: () => _handleDeleteLatestTagAction.call(setState),
onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, setState),
onSubmitted: (value) => _handleSubmitTagAction.call(value, setState),
tagBuilder: (context, index) {
final currentEmailAddress = _currentListEmailAddress.elementAt(index);
final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last;
return DefaultAutocompleteTagItemWidget(
field: widget.field,
currentEmailAddress: currentEmailAddress,
currentListEmailAddress: _currentListEmailAddress,
collapsedListEmailAddress: _collapsedListEmailAddress,
isLatestEmail: isLatestEmail,
isCollapsed: _isCollapse,
isLatestTagFocused: _lastTagFocused,
iconClose: _imagePaths.icClose,
onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, setState),
onShowFullAction: widget.onShowFullListEmailAddressAction,
);
},
onTagChanged: (tag) => _handleOnTagChangeAction.call(tag, setState),
findSuggestions: (queryString) => _findSuggestions(
queryString,
limit: AppConfig.defaultLimitAutocomplete,
),
isLoadMoreOnlyOnce: true,
isLoadMoreReplaceAllOld: false,
loadMoreSuggestions: _findSuggestions,
suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) {
return RecipientSuggestionItemWidget(
imagePaths: _imagePaths,
suggestionState: suggestionEmailAddress.state,
emailAddress: suggestionEmailAddress.emailAddress,
suggestionValid: suggestionValid,
highlight: highlight,
onSelectedAction: (emailAddress) {
setState(() => _currentListEmailAddress.add(emailAddress));
_updateListEmailAddressAction();
tagEditorState.resetTextField();
tagEditorState.closeSuggestionBox();
},
);
},
onHandleKeyEventAction: (event) {
if (event is KeyDownEvent) {
switch (event.logicalKey) {
case LogicalKeyboardKey.tab:
widget.nextFocusNode?.requestFocus();
break;
default:
break;
}
}
},
);
}
);
})
),
),
],
);
})
);
}
bool get _isCollapse => _currentListEmailAddress.length > 1 && widget.expandMode == ExpandMode.COLLAPSE;
List<EmailAddress> get _collapsedListEmailAddress => _isCollapse
? _currentListEmailAddress.sublist(0, 1)
: _currentListEmailAddress;
FutureOr<List<SuggestionEmailAddress>> _findSuggestions(String query, {int? limit}) async {
if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) {
return [];
}
final processedQuery = query.trim();
if (processedQuery.isEmpty) {
return [];
}
final tmailSuggestion = List<SuggestionEmailAddress>.empty(growable: true);
if (processedQuery.length >= widget.minInputLengthAutocomplete &&
widget.onSuggestionEmailAddress != null) {
final listEmailAddress = await widget.onSuggestionEmailAddress!(
processedQuery,
limit: limit,
);
final listSuggestionEmailAddress = listEmailAddress.map((emailAddress) => _toSuggestionEmailAddress(emailAddress, _currentListEmailAddress));
tmailSuggestion.addAll(listSuggestionEmailAddress);
}
tmailSuggestion.addAll(_matchedSuggestionEmailAddress(processedQuery, _currentListEmailAddress));
final currentTextOnTextField = widget.controller?.text ?? '';
if (currentTextOnTextField.isEmpty) {
return [];
}
return tmailSuggestion.toSet().toList();
}
bool _isDuplicated(String inputEmail) {
if (inputEmail.isEmpty) {
return false;
}
return _currentListEmailAddress
.map((emailAddress) => emailAddress.email)
.whereNotNull()
.contains(inputEmail);
}
SuggestionEmailAddress _toSuggestionEmailAddress(EmailAddress item, List<EmailAddress> addedEmailAddresses) {
if (addedEmailAddresses.contains(item)) {
return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated);
} else {
return SuggestionEmailAddress(item);
}
}
Iterable<SuggestionEmailAddress> _matchedSuggestionEmailAddress(String query, List<EmailAddress> addedEmailAddress) {
return addedEmailAddress
.where((addedMail) => addedMail.emailAddress.contains(query))
.map((emailAddress) => SuggestionEmailAddress(
emailAddress,
state: SuggestionEmailState.duplicated
));
}
void _handleGapBetweenTagChangedAndFindSuggestion() {
log('_TextFieldAutocompleteEmailAddressWebState::_handleGapBetweenTagChangedAndFindSuggestion:Timeout');
}
void _updateListEmailAddressAction() {
widget.onUpdateListEmailAddressAction?.call(
widget.field,
_currentListEmailAddress
);
}
void _handleFocusTagAction(bool focused, StateSetter stateSetter) {
if (mounted) {
stateSetter(() => _lastTagFocused = focused);
}
}
void _handleDeleteLatestTagAction(StateSetter stateSetter) {
if (_currentListEmailAddress.isNotEmpty) {
stateSetter(_currentListEmailAddress.removeLast);
_updateListEmailAddressAction();
}
}
void _handleDeleteTagAction(EmailAddress emailAddress, StateSetter stateSetter) {
if (_currentListEmailAddress.isNotEmpty) {
stateSetter(() => _currentListEmailAddress.remove(emailAddress));
_updateListEmailAddressAction();
}
}
void _handleSelectOptionAction(
SuggestionEmailAddress suggestionEmailAddress,
StateSetter stateSetter
) {
if (!_isDuplicated(suggestionEmailAddress.emailAddress.emailAddress)) {
stateSetter(() => _currentListEmailAddress.add(suggestionEmailAddress.emailAddress));
_updateListEmailAddressAction();
}
}
void _handleSubmitTagAction(
String value,
StateSetter stateSetter
) {
final textTrim = value.trim();
if (textTrim.isEmpty) {
widget.onSearchAction?.call();
return;
}
if (!_isDuplicated(textTrim)) {
stateSetter(() => _currentListEmailAddress.add(EmailAddress(null, textTrim)));
_updateListEmailAddressAction();
}
}
void _handleOnTagChangeAction(
String value,
StateSetter stateSetter
) {
final textTrim = value.trim();
if (!_isDuplicated(textTrim)) {
stateSetter(() => _currentListEmailAddress.add(EmailAddress(null, textTrim)));
_updateListEmailAddressAction();
}
_gapBetweenTagChangedAndFindSuggestion = Timer(
const Duration(seconds: 1),
_handleGapBetweenTagChangedAndFindSuggestion
);
}
double? _getSuggestionBoxWidth(double maxWidth) {
if (maxWidth < ResponsiveUtils.minTabletWidth) {
final newWidth = min(maxWidth, 300.0);
return newWidth;
} else {
return null;
}
}
void _handleAcceptDraggableEmailAddressAction(
DraggableEmailAddress draggableEmailAddress,
StateSetter stateSetter
) {
log('_TextFieldAutocompleteEmailAddressWebState::_handleAcceptDraggableEmailAddressAction:draggableEmailAddress = $draggableEmailAddress');
if (draggableEmailAddress.filterField != widget.field) {
if (!_currentListEmailAddress.contains(draggableEmailAddress.emailAddress)) {
stateSetter(() {
_currentListEmailAddress.add(draggableEmailAddress.emailAddress);
_isDragging = false;
});
_updateListEmailAddressAction();
} else if (_isDragging) {
stateSetter(() => _isDragging = false);
}
widget.onRemoveDraggableEmailAddressAction?.call(draggableEmailAddress);
} else if (_isDragging) {
stateSetter(() => _isDragging = false);
}
}
}
@@ -215,7 +215,6 @@ void main() {
expect(advancedFilterController.notKeyWordFilterInputController.text, equals('hello,nice'));
expect(advancedFilterController.receiveTimeType.value, equals(EmailReceiveTimeType.last7Days));
expect(advancedFilterController.sortOrderType.value, equals(EmailSortOrderType.oldest));
expect(advancedFilterController.mailBoxFilterInputController.text, equals('mailbox1'));
expect(advancedFilterController.hasAttachment.value, equals(true));
expect(advancedFilterController.listFromEmailAddress, equals([EmailAddress(null, 'user1@example.com')]));
expect(advancedFilterController.listToEmailAddress, equals([EmailAddress(null, 'user2@example.com')]));