From b3ec90b507cb3de9d1668916157a492af3ba6abb Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 3 Mar 2023 14:20:48 +0700 Subject: [PATCH] TF-1477 Improve UX for advanced and quick search --- .../base/mixin/date_range_picker_mixin.dart | 93 +++++++++ .../base/widget/drop_down_button_widget.dart | 8 +- .../widget/popup_item_no_icon_widget.dart | 63 ++++++ .../presentation/action/dashboard_action.dart | 22 ++ .../advanced_filter_controller.dart | 192 ++++++------------ .../mailbox_dashboard_controller.dart | 66 +++--- .../controller/search_controller.dart | 151 ++++++++------ .../mailbox_dashboard_view_web.dart | 95 ++++----- .../model/search/advanced_search_filter.dart | 2 +- .../model/search/email_receive_time_type.dart | 47 ++++- .../model/search/quick_search_filter.dart | 54 ++++- .../model/search/search_email_filter.dart | 66 ++---- .../advanced_search_filter_form.dart | 76 +++---- .../date_drop_down_button.dart | 130 ++++++++++++ .../icon_open_advanced_search_widget.dart | 6 +- .../widgets/search_input_form_widget.dart | 43 ++-- .../presentation/thread_controller.dart | 130 ++++++------ lib/l10n/intl_messages.arb | 8 +- lib/main/localizations/app_localizations.dart | 7 - model/lib/mailbox/presentation_mailbox.dart | 8 + 20 files changed, 764 insertions(+), 503 deletions(-) create mode 100644 lib/features/base/mixin/date_range_picker_mixin.dart create mode 100644 lib/features/base/widget/popup_item_no_icon_widget.dart create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart diff --git a/lib/features/base/mixin/date_range_picker_mixin.dart b/lib/features/base/mixin/date_range_picker_mixin.dart new file mode 100644 index 000000000..8886eed3c --- /dev/null +++ b/lib/features/base/mixin/date_range_picker_mixin.dart @@ -0,0 +1,93 @@ + +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/app_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_date_range_picker/multiple_view_date_range_picker.dart'; +import 'package:get/get.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +mixin DateRangePickerMixin { + + void showMultipleViewDateRangePicker( + BuildContext context, + DateTime? initStartDate, + DateTime? initEndDate, + {Function(DateTime? startDate, DateTime? endDate)? onCallbackAction} + ) { + showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: '', + barrierColor: Colors.black54, + pageBuilder: (context, _, __) { + return Dialog( + elevation: 0, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18.0)), + child: PointerInterceptor(child: MultipleViewDateRangePicker( + confirmText: AppLocalizations.of(context).setDate, + cancelText: AppLocalizations.of(context).cancel, + last7daysTitle: AppLocalizations.of(context).last7Days, + last30daysTitle: AppLocalizations.of(context).last30Days, + last6monthsTitle: AppLocalizations.of(context).last6Months, + lastYearTitle: AppLocalizations.of(context).lastYears, + startDate: initStartDate, + endDate: initEndDate, + setDateActionCallback: ({startDate, endDate}) => + _handleSelectDateRangeResult( + context, + startDate, + endDate, + onCallbackAction: onCallbackAction + ) + )) + ); + } + ); + } + + void _handleSelectDateRangeResult( + BuildContext context, + DateTime? startDate, + DateTime? endDate, + {Function(DateTime? startDate, DateTime? endDate)? onCallbackAction} + ) { + final _appToast = Get.find(); + final _imagePaths = Get.find(); + + if (startDate == null) { + _appToast.showToastWithIcon( + context, + textColor: Colors.black, + message: AppLocalizations.of(context).toastMessageErrorWhenSelectStartDateIsEmpty, + icon: _imagePaths.icNotConnection + ); + return; + } + + if (endDate == null) { + _appToast.showToastWithIcon( + context, + textColor: Colors.black, + message: AppLocalizations.of(context).toastMessageErrorWhenSelectEndDateIsEmpty, + icon: _imagePaths.icNotConnection + ); + return; + } + + if (endDate.isBefore(startDate)) { + _appToast.showToastWithIcon( + context, + textColor: Colors.black, + message: AppLocalizations.of(context).toastMessageErrorWhenSelectDateIsInValid, + icon: _imagePaths.icNotConnection + ); + return; + } + + popBack(); + onCallbackAction?.call(startDate, endDate); + } +} \ No newline at end of file diff --git a/lib/features/base/widget/drop_down_button_widget.dart b/lib/features/base/widget/drop_down_button_widget.dart index a98ce6186..5b023b0f9 100644 --- a/lib/features/base/widget/drop_down_button_widget.dart +++ b/lib/features/base/widget/drop_down_button_widget.dart @@ -28,7 +28,7 @@ class DropDownButtonWidget extends StatelessWidget { final double radiusButton; final double opacity; final Widget? iconArrowDown; - final Color? colorButton; + final Color colorButton; final String tooltip; final double? dropdownWidth; final double? dropdownMaxHeight; @@ -36,7 +36,7 @@ class DropDownButtonWidget extends StatelessWidget { const DropDownButtonWidget({ Key? key, required this.items, - required this.itemSelected, + this.itemSelected, this.onChanged, this.onMenuStateChange, this.supportHint = false, @@ -112,7 +112,7 @@ class DropDownButtonWidget extends StatelessWidget { color: AppColor.colorInputBorderCreateMailbox, width: 1, ), - color: colorButton ?? AppColor.colorInputBackgroundCreateMailbox, + color: colorButton, ), padding: const EdgeInsets.only(left: 12, right: 10), child: Row(children: [ @@ -139,7 +139,7 @@ class DropDownButtonWidget extends StatelessWidget { color: AppColor.colorInputBorderCreateMailbox, width: 1, ), - color: colorButton ?? AppColor.colorInputBackgroundCreateMailbox, + color: colorButton, ), itemHeight: heightItem, buttonHeight: heightItem, diff --git a/lib/features/base/widget/popup_item_no_icon_widget.dart b/lib/features/base/widget/popup_item_no_icon_widget.dart new file mode 100644 index 000000000..4fbea9c64 --- /dev/null +++ b/lib/features/base/widget/popup_item_no_icon_widget.dart @@ -0,0 +1,63 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; + +class PopupItemNoIconWidget extends StatelessWidget { + + final String _nameAction; + final String? svgIconSelected; + final bool isSelected; + final double? maxWidth; + final TextStyle? styleName; + final EdgeInsets? padding; + final VoidCallback? onCallbackAction; + + const PopupItemNoIconWidget( + this._nameAction, + { + Key? key, + this.isSelected = false, + this.svgIconSelected, + this.maxWidth, + this.styleName, + this.padding, + this.onCallbackAction + } + ) : super(key: key); + + @override + Widget build(BuildContext context) { + return PointerInterceptor( + child: InkWell( + onTap: onCallbackAction, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: SizedBox( + width: maxWidth, + child: Row(children: [ + Expanded(child: Text( + _nameAction, + style: const TextStyle( + fontSize: 17, + color: Colors.black, + fontWeight: FontWeight.normal + ) + )), + if (isSelected && svgIconSelected != null) + ...[ + const SizedBox(width: 12), + SvgPicture.asset( + svgIconSelected!, + width: 24, + height: 24, + fit: BoxFit.fill + ), + ] + ]), + ), + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart index 787beaa8c..39d6da89a 100644 --- a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart +++ b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart @@ -4,6 +4,7 @@ import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; @@ -134,4 +135,25 @@ class OpenMailboxAction extends DashBoardAction { @override List get props => [context, presentationMailbox]; +} + +class SelectDateRangeToAdvancedSearch extends DashBoardAction { + + final DateTime? startDate; + final DateTime? endDate; + + SelectDateRangeToAdvancedSearch(this.startDate, this.endDate); + + @override + List get props => [startDate, endDate]; +} + +class ClearDateRangeToAdvancedSearch extends DashBoardAction { + + final EmailReceiveTimeType receiveTime; + + ClearDateRangeToAdvancedSearch(this.receiveTime); + + @override + List get props => [receiveTime]; } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart index d2544fb7e..04d9e9156 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart @@ -2,14 +2,12 @@ import 'package:collection/collection.dart'; import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_date_range_picker/multiple_view_date_range_picker.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart'; import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart'; @@ -37,28 +35,24 @@ class AdvancedFilterController extends BaseController { final hasAttachment = false.obs; final lastTextForm = ''.obs; final lastTextTo = ''.obs; + final startDate = Rxn(); + final endDate = Rxn(); + TextEditingController subjectFilterInputController = TextEditingController(); TextEditingController hasKeyWordFilterInputController = TextEditingController(); TextEditingController notKeyWordFilterInputController = TextEditingController(); - TextEditingController dateFilterInputController = TextEditingController(); TextEditingController mailBoxFilterInputController = TextEditingController(); ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.tMailContact; final SearchController searchController = Get.find(); final MailboxDashBoardController _mailboxDashBoardController = Get.find(); - final _appToast = Get.find(); - final _imagePaths = Get.find(); SearchEmailFilter get searchEmailFilter => searchController.searchEmailFilter.value; final focusManager = InputFieldFocusManager.initial(); - DateTime? _startDate, _endDate; - - DateTime? get startDate => _startDate; - - DateTime? get endDate => _endDate; + PresentationMailbox? _destinationMailboxSelected; late Worker _dashboardActionWorker; @@ -83,17 +77,15 @@ class AdvancedFilterController extends BaseController { void cleanSearchFilter(BuildContext context) { searchController.clearSearchFilter(); - dateFilterSelectedFormAdvancedSearch.value = EmailReceiveTimeType.allTime; - clearDateRangeOfFilter(); + _updateDateRangeTime(EmailReceiveTimeType.allTime); subjectFilterInputController.text = ''; hasKeyWordFilterInputController.text = ''; notKeyWordFilterInputController.text = ''; - dateFilterInputController.text = ''; hasAttachment.value = false; + _destinationMailboxSelected = null; searchController.deactivateAdvancedSearch(); searchController.isAdvancedSearchViewOpen.toggle(); - _mailboxDashBoardController.searchEmail( - context, StringConvert.writeNullToEmpty(searchEmailFilter.text?.value)); + _mailboxDashBoardController.searchEmail(context, StringConvert.writeNullToEmpty(searchEmailFilter.text?.value)); } void _updateFilterEmailFromAdvancedSearchView() { @@ -127,11 +119,12 @@ class AdvancedFilterController extends BaseController { } searchController.updateFilterEmail( + mailbox: _destinationMailboxSelected, subjectOption: optionOf(subjectFilterInputController.text), emailReceiveTimeType: dateFilterSelectedFormAdvancedSearch.value, hasAttachment: hasAttachment.value, - endDate: _endDate.toUTCDate(), - startDate: _startDate.toUTCDate() + startDateOption: optionOf(startDate.value?.toUTCDate()), + endDateOption: optionOf(endDate.value?.toUTCDate()) ); } @@ -150,9 +143,9 @@ class AdvancedFilterController extends BaseController { context: context, arguments: arguments, onSelectedMailbox: (destinationMailbox) { - searchController.updateFilterEmail(mailbox: destinationMailbox); - mailBoxFilterInputController.text = - StringConvert.writeNullToEmpty(destinationMailbox.name?.name); + _destinationMailboxSelected = destinationMailbox; + final mailboxName = destinationMailbox.name?.name; + mailBoxFilterInputController.text = StringConvert.writeNullToEmpty(mailboxName); }); } else { final destinationMailbox = await push( @@ -160,9 +153,9 @@ class AdvancedFilterController extends BaseController { arguments: arguments); if (destinationMailbox is PresentationMailbox) { - searchController.updateFilterEmail(mailbox: destinationMailbox); - mailBoxFilterInputController.text = - StringConvert.writeNullToEmpty(destinationMailbox.name?.name); + _destinationMailboxSelected = destinationMailbox; + final mailboxName = destinationMailbox.name?.name; + mailBoxFilterInputController.text = StringConvert.writeNullToEmpty(mailboxName); } } } @@ -176,8 +169,7 @@ class AdvancedFilterController extends BaseController { searchController.deactivateAdvancedSearch(); } if (!isAdvancedSearchHasApplied) { - final newSearchEmailFilter = searchController.searchEmailFilter.value.clearBeforeDate(); - searchController.searchEmailFilter.value = newSearchEmailFilter; + searchController.updateFilterEmail(beforeOption: const None()); } searchController.isAdvancedSearchViewOpen.toggle(); _mailboxDashBoardController.searchEmail( @@ -259,111 +251,55 @@ class AdvancedFilterController extends BaseController { } void initSearchFilterField(BuildContext context) { - searchController.updateFilterEmail( - mailbox: PresentationMailbox.unifiedMailbox); - subjectFilterInputController.text = - StringConvert.writeNullToEmpty(searchEmailFilter.subject); - hasKeyWordFilterInputController.text = StringConvert.writeNullToEmpty( - searchEmailFilter.text?.value); - notKeyWordFilterInputController.text = StringConvert.writeNullToEmpty( - searchEmailFilter.notKeyword.firstOrNull); - dateFilterInputController.text = StringConvert.writeNullToEmpty( - searchEmailFilter.emailReceiveTimeType.getTitle( - context, - startDate: _startDate, - endDate: _endDate)); - mailBoxFilterInputController.text = - StringConvert.writeNullToEmpty(searchEmailFilter.mailbox?.name?.name); - dateFilterSelectedFormAdvancedSearch.value = - searchEmailFilter.emailReceiveTimeType; + subjectFilterInputController.text = StringConvert.writeNullToEmpty(searchEmailFilter.subject); + hasKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(searchEmailFilter.text?.value); + notKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(searchEmailFilter.notKeyword.firstOrNull); + dateFilterSelectedFormAdvancedSearch.value = searchEmailFilter.emailReceiveTimeType; + _destinationMailboxSelected = searchEmailFilter.mailbox; + if (searchEmailFilter.mailbox == null) { + mailBoxFilterInputController.text = AppLocalizations.of(context).allMailboxes; + } else { + mailBoxFilterInputController.text = StringConvert.writeNullToEmpty(searchEmailFilter.mailbox?.name?.name); + } hasAttachment.value = searchEmailFilter.hasAttachment; } void selectDateRange(BuildContext context) { - showGeneralDialog( - context: context, - barrierDismissible: true, - barrierLabel: '', - barrierColor: Colors.black54, - pageBuilder: (context, animation, secondaryAnimation) { - return Dialog( - elevation: 0, - backgroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18.0)), - child: PointerInterceptor( - child: MultipleViewDateRangePicker( - confirmText: AppLocalizations.of(context).setDate, - cancelText: AppLocalizations.of(context).cancel, - last7daysTitle: AppLocalizations.of(context).last7Days, - last30daysTitle: AppLocalizations.of(context).last30Days, - last6monthsTitle: AppLocalizations.of(context).last6Months, - lastYearTitle: AppLocalizations.of(context).lastYears, - startDate: _startDate, - endDate: _endDate, - setDateActionCallback: ({startDate, endDate}) { - _handleSelectDateRangeResult(context, startDate, endDate); - }, - ), - ) - ); - } + searchController.showMultipleViewDateRangePicker( + context, + startDate.value, + endDate.value, + onCallbackAction: (startDate, endDate) => + _updateDateRangeTime( + EmailReceiveTimeType.customRange, + newStartDate: startDate, + newEndDate: endDate + ) ); } - void _handleSelectDateRangeResult( - BuildContext context, - DateTime? startDate, - DateTime? endDate - ) { - log('AdvancedFilterController::_handleSelectDateRangeResult(): startDate: $startDate'); - log('AdvancedFilterController::_handleSelectDateRangeResult(): endDate: $endDate'); - if (startDate == null) { - _appToast.showToastWithIcon( - context, - textColor: Colors.black, - message: AppLocalizations.of(context).toastMessageErrorWhenSelectStartDateIsEmpty, - icon: _imagePaths.icNotConnection); - return; - } - if (endDate == null) { - _appToast.showToastWithIcon( - context, - textColor: Colors.black, - message: AppLocalizations.of(context).toastMessageErrorWhenSelectEndDateIsEmpty, - icon: _imagePaths.icNotConnection); - return; - } - - if (endDate.isBefore(startDate)) { - _appToast.showToastWithIcon( - context, - textColor: Colors.black, - message: AppLocalizations.of(context).toastMessageErrorWhenSelectDateIsInValid, - icon: _imagePaths.icNotConnection); - return; - } - - _startDate = startDate; - _endDate = endDate; - dateFilterSelectedFormAdvancedSearch.value = EmailReceiveTimeType.customRange; - dateFilterInputController.text = EmailReceiveTimeType.customRange.getTitle( - context, - startDate: startDate, - endDate: endDate); - dateFilterSelectedFormAdvancedSearch.refresh(); - - popBack(); + void _updateDateRangeTime(EmailReceiveTimeType receiveTime, {DateTime? newStartDate, DateTime? newEndDate}) { + startDate.value = newStartDate; + endDate.value = newEndDate; + dateFilterSelectedFormAdvancedSearch.value = receiveTime; } - void clearDateRangeOfFilter() { - _startDate = null; - _endDate = null; - - searchController.searchEmailFilter.value = - searchController.searchEmailFilter.value.withDateRange( - startDate: _startDate.toUTCDate(), - endDate: _endDate.toUTCDate()); + void updateReceiveDateSearchFilter(BuildContext context, EmailReceiveTimeType receiveTime) { + if (receiveTime == EmailReceiveTimeType.customRange) { + searchController.showMultipleViewDateRangePicker( + context, + startDate.value, + endDate.value, + onCallbackAction: (startDate, endDate) => + _updateDateRangeTime( + EmailReceiveTimeType.customRange, + newStartDate: startDate, + newEndDate: endDate + ) + ); + } else { + _updateDateRangeTime(receiveTime); + } } void _resetAllToOriginalValue() { @@ -371,15 +307,14 @@ class AdvancedFilterController extends BaseController { hasAttachment.value = false; lastTextForm.value = ''; lastTextTo.value = ''; - _startDate = null; - _endDate = null; + startDate.value = null; + endDate.value = null; } void _clearAllTextFieldInput() { subjectFilterInputController.clear(); hasKeyWordFilterInputController.clear(); notKeyWordFilterInputController.clear(); - dateFilterInputController.clear(); mailBoxFilterInputController.clear(); } @@ -389,6 +324,14 @@ class AdvancedFilterController extends BaseController { (action) { if (action is ClearAllFieldOfAdvancedSearchAction) { _handleClearAllFieldOfAdvancedSearch(); + } else if (action is SelectDateRangeToAdvancedSearch) { + _updateDateRangeTime( + EmailReceiveTimeType.customRange, + newStartDate: action.startDate, + newEndDate: action.endDate + ); + } else if (action is ClearDateRangeToAdvancedSearch) { + _updateDateRangeTime(action.receiveTime); } } ); @@ -410,7 +353,6 @@ class AdvancedFilterController extends BaseController { hasKeyWordFilterInputController.dispose(); notKeyWordFilterInputController.dispose(); mailBoxFilterInputController.dispose(); - dateFilterInputController.dispose(); _unregisterWorkerListener(); super.onClose(); } diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 405906510..7555c0e45 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -73,6 +73,7 @@ import 'package:tmail_ui_user/features/manage_account/domain/state/update_vacati import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_vacation_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_vacation_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/account_menu_item.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/manage_account_arguments.dart'; @@ -535,9 +536,6 @@ class MailboxDashBoardController extends ReloadableController { if (_searchInsideEmailDetailedViewIsActive(context)) { _closeEmailDetailedView(); } - if (value.isEmpty){ - searchController.setEmailReceiveTimeType(null); - } _unSelectedMailbox(); } @@ -1281,23 +1279,13 @@ class MailboxDashBoardController extends ReloadableController { } } - void selectQuickSearchFilter({ - required QuickSearchFilter quickSearchFilter, - bool fromSuggestionBox = false, - }) => searchController.selectQuickSearchFilter( - quickSearchFilter: quickSearchFilter, - userProfile: userProfile.value!, - fromSuggestionBox: fromSuggestionBox, - ); + void selectQuickSearchFilter(QuickSearchFilter filter) { + return searchController.selectQuickSearchFilter(filter, userProfile.value!); + } - bool checkQuickSearchFilterSelected({ - required QuickSearchFilter quickSearchFilter, - bool fromSuggestionBox = false, - }) => searchController.checkQuickSearchFilterSelected( - quickSearchFilter: quickSearchFilter, - userProfile: userProfile.value!, - fromSuggestionBox: fromSuggestionBox, - ); + void addFilterToSuggestionForm(QuickSearchFilter filter) { + searchController.addFilterToSuggestionForm(filter); + } Future> quickSearchEmails() => searchController.quickSearchEmails(accountId: accountId.value!); @@ -1353,26 +1341,44 @@ class MailboxDashBoardController extends ReloadableController { void selectQuickSearchFilterAction(QuickSearchFilter filter) { log('MailboxDashBoardController::selectQuickSearchFilterAction(): filter: $filter'); - selectQuickSearchFilter(quickSearchFilter: filter); + selectQuickSearchFilter(filter); dispatchAction(StartSearchEmailAction()); } - void selectReceiveTimeQuickSearchFilter(EmailReceiveTimeType? emailReceiveTimeType) { + void selectReceiveTimeQuickSearchFilter(BuildContext context, EmailReceiveTimeType receiveTime) { + log('MailboxDashBoardController::selectReceiveTimeQuickSearchFilter():receiveTime: $receiveTime'); popBack(); - if (emailReceiveTimeType != null) { - searchController.updateFilterEmail(emailReceiveTimeType: emailReceiveTimeType); + if (receiveTime == EmailReceiveTimeType.customRange) { + searchController.showMultipleViewDateRangePicker( + context, + searchController.startDateFiltered, + searchController.endDateFiltered, + onCallbackAction: (startDate, endDate) { + dispatchAction(SelectDateRangeToAdvancedSearch(startDate, endDate)); + searchController.updateFilterEmail( + emailReceiveTimeType: receiveTime, + startDateOption: optionOf(startDate?.toUTCDate()), + endDateOption: optionOf(startDate?.toUTCDate()), + beforeOption: const None() + ); + dispatchAction(StartSearchEmailAction()); + } + ); } else { - searchController.updateFilterEmail(emailReceiveTimeType: EmailReceiveTimeType.allTime); + dispatchAction(ClearDateRangeToAdvancedSearch(receiveTime)); + searchController.updateFilterEmail( + emailReceiveTimeType: receiveTime, + startDateOption: const None(), + endDateOption: const None(), + beforeOption: const None() + ); + dispatchAction(StartSearchEmailAction()); } - searchController.setEmailReceiveTimeType(emailReceiveTimeType); - searchController.updateFilterEmail(); - if (searchController.searchQuery == null){ - searchController.updateFilterEmail(text: SearchQuery.initial()); - } - dispatchAction(StartSearchEmailAction()); } + + bool get isMailboxTrash => selectedMailbox.value?.isTrash == true; void emptyTrashAction(BuildContext context) { diff --git a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart index 8ceb89a79..c544f482c 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart @@ -1,17 +1,26 @@ import 'dart:async'; -import 'package:core/core.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart'; +import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; -import 'package:model/model.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/email_filter_condition_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:model/user/user_profile.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/base/mixin/date_range_picker_mixin.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_all_recent_search_latest_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/quick_search_email_state.dart'; @@ -27,7 +36,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/search_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart'; -class SearchController extends BaseController { +class SearchController extends BaseController with DateRangePickerMixin { final QuickSearchEmailInteractor _quickSearchEmailInteractor; final SaveRecentSearchInteractor _saveRecentSearchInteractor; final GetAllRecentSearchLatestInteractor _getAllRecentSearchLatestInteractor; @@ -38,7 +47,7 @@ class SearchController extends BaseController { final searchEmailFilter = SearchEmailFilter.initial().obs; final searchState = SearchState.initial().obs; final isAdvancedSearchViewOpen = false.obs; - final emailReceiveTimeType = Rxn(); + final listFilterOnSuggestionForm = RxList(); final simpleSearchIsActivated = RxBool(false); final advancedSearchIsActivated = RxBool(false); final autoFocus = RxBool(true); @@ -67,53 +76,78 @@ class SearchController extends BaseController { searchEmailFilter.value = SearchEmailFilter.initial(); } - void selectQuickSearchFilter({ - required QuickSearchFilter quickSearchFilter, - required UserProfile userProfile, - bool fromSuggestionBox = false, - }) { - final quickSearchFilterSelected = checkQuickSearchFilterSelected( - userProfile: userProfile, - quickSearchFilter: quickSearchFilter, - fromSuggestionBox: fromSuggestionBox, - ); + void selectQuickSearchFilter(QuickSearchFilter quickSearchFilter, UserProfile userProfile) { + final isFilterSelected = quickSearchFilter.isSelected(searchEmailFilter.value, userProfile); switch (quickSearchFilter) { case QuickSearchFilter.hasAttachment: - updateFilterEmail(hasAttachment: !quickSearchFilterSelected); + updateFilterEmail(hasAttachment: !isFilterSelected); return; case QuickSearchFilter.last7Days: - if (quickSearchFilterSelected) { - setEmailReceiveTimeType(null); - updateFilterEmail(emailReceiveTimeType: EmailReceiveTimeType.allTime); - } else { - setEmailReceiveTimeType(EmailReceiveTimeType.last7Days); - updateFilterEmail(emailReceiveTimeType: EmailReceiveTimeType.last7Days); - } + updateFilterEmail(emailReceiveTimeType: EmailReceiveTimeType.allTime); return; case QuickSearchFilter.fromMe: - quickSearchFilterSelected - ? searchEmailFilter.value.from.removeWhere((e) => e == userProfile.email) - : searchEmailFilter.value.from.add(userProfile.email); + isFilterSelected + ? searchEmailFilter.value.from.removeWhere((e) => e == userProfile.email) + : searchEmailFilter.value.from.add(userProfile.email); updateFilterEmail(from: searchEmailFilter.value.from); return; } } - Future> quickSearchEmails({required AccountId accountId}) async { - return await _quickSearchEmailInteractor - .execute(accountId, - limit: UnsignedInt(5), - sort: {}..add( - EmailComparator(EmailComparatorProperty.receivedAt) - ..setIsAscending(false)), - filter: searchEmailFilter.value.mappingToEmailFilterCondition(), - properties: ThreadConstants.propertiesQuickSearch) - .then((result) => result.fold( - (failure) => [], - (success) => success is QuickSearchEmailSuccess - ? success.emailList - : [])); + void addFilterToSuggestionForm(QuickSearchFilter filter) { + if (listFilterOnSuggestionForm.contains(filter)) { + listFilterOnSuggestionForm.remove(filter); + } else { + listFilterOnSuggestionForm.add(filter); + } + } + + Future> quickSearchEmails({ + required AccountId accountId, + UserProfile? userProfile + }) async { + return await _quickSearchEmailInteractor.execute( + accountId, + limit: UnsignedInt(5), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt) + ..setIsAscending(false)), + filter: _mappingToFilterOnSuggestionForm(userProfile), + properties: ThreadConstants.propertiesQuickSearch + ).then((result) => result.fold( + (failure) => [], + (success) => success is QuickSearchEmailSuccess + ? success.emailList + : [] + )); + } + + Filter? _mappingToFilterOnSuggestionForm(UserProfile? userProfile) { + final searchText = searchEmailFilter.value.text?.value.trim(); + + final filterCondition = EmailFilterCondition( + text: searchText?.isNotEmpty == true ? searchText : null, + after: listFilterOnSuggestionForm.contains(QuickSearchFilter.last7Days) + ? EmailReceiveTimeType.last7Days.toOldestUTCDate() + : null, + before: listFilterOnSuggestionForm.contains(QuickSearchFilter.last7Days) + ? EmailReceiveTimeType.last7Days.toLatestUTCDate() + : null, + hasAttachment: listFilterOnSuggestionForm.contains(QuickSearchFilter.hasAttachment) + ? true + : null + ); + + final listEmailCondition = { + if (filterCondition.hasCondition) filterCondition, + if (listFilterOnSuggestionForm.contains(QuickSearchFilter.fromMe) && userProfile != null) + LogicFilterOperator(Operator.AND, {EmailFilterCondition(from: userProfile.email)}) + }; + + return listEmailCondition.isNotEmpty + ? LogicFilterOperator(Operator.AND, listEmailCondition) + : null; } void updateFilterEmail({ @@ -125,9 +159,9 @@ class SearchController extends BaseController { PresentationMailbox? mailbox, EmailReceiveTimeType? emailReceiveTimeType, bool? hasAttachment, - UTCDate? before, - UTCDate? startDate, - UTCDate? endDate, + Option? beforeOption, + Option? startDateOption, + Option? endDateOption }) { searchEmailFilter.value = searchEmailFilter.value.copyWith( from: from, @@ -138,9 +172,9 @@ class SearchController extends BaseController { mailbox: mailbox, emailReceiveTimeType: emailReceiveTimeType, hasAttachment: hasAttachment, - before: before, - startDate: startDate, - endDate: endDate, + beforeOption: beforeOption, + startDateOption: startDateOption, + endDateOption: endDateOption, ); searchEmailFilter.refresh(); } @@ -159,6 +193,12 @@ class SearchController extends BaseController { }); } + EmailReceiveTimeType get receiveTimeFiltered => searchEmailFilter.value.emailReceiveTimeType; + + DateTime? get startDateFiltered => searchEmailFilter.value.startDate?.value.toLocal(); + + DateTime? get endDateFiltered => searchEmailFilter.value.endDate?.value.toLocal(); + bool isSearchActive() => searchState.value.searchStatus == SearchStatus.ACTIVE; @@ -188,21 +228,15 @@ class SearchController extends BaseController { searchInputController.text = value; } - bool checkQuickSearchFilterSelected({ - required QuickSearchFilter quickSearchFilter, - required UserProfile userProfile, - bool fromSuggestionBox = false, - }) { + bool checkQuickSearchFilterSelected(QuickSearchFilter quickSearchFilter, UserProfile userProfile) { switch (quickSearchFilter) { case QuickSearchFilter.hasAttachment: return searchEmailFilter.value.hasAttachment == true; case QuickSearchFilter.last7Days: - if (emailReceiveTimeType.value != null) { - return true; - } - return searchEmailFilter.value.emailReceiveTimeType == EmailReceiveTimeType.last7Days; + return true; case QuickSearchFilter.fromMe: - return searchEmailFilter.value.from.contains( userProfile.email) && searchEmailFilter.value.from.length == 1; + return searchEmailFilter.value.from.contains(userProfile.email) && + searchEmailFilter.value.from.length == 1; } } @@ -220,11 +254,7 @@ class SearchController extends BaseController { : [])); } - void setEmailReceiveTimeType(EmailReceiveTimeType? receiveTimeType) { - emailReceiveTimeType.value = receiveTimeType; - } - - showAdvancedFilterView(BuildContext context) async { + void showAdvancedFilterView(BuildContext context) async { selectOpenAdvanceSearch(); if (_responsiveUtils.isMobile(context)) { await showAdvancedSearchFilterBottomSheet(context); @@ -259,7 +289,6 @@ class SearchController extends BaseController { void _clearAllTextInputSimpleSearch() { searchInputController.clear(); searchFocus.unfocus(); - emailReceiveTimeType.value = null; } void disableAllSearchEmail() { diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 0988c4f93..28369ca6e 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -4,6 +4,7 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/widget/popup_item_no_icon_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart'; import 'package:tmail_ui_user/features/email/presentation/email_view.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; @@ -541,8 +542,9 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { QuickSearchFilter filter ) { return Obx(() { - final quickSearchFilterSelected = controller.checkQuickSearchFilterSelected( - quickSearchFilter: filter, + final isFilterSelected = filter.isSelected( + controller.searchController.searchEmailFilter.value, + controller.userProfile.value ); return Padding( @@ -563,35 +565,40 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { screenSize.width - offset.dx, screenSize.height - offset.dy, ); - controller.openPopupMenuAction(context, position, - popupMenuEmailReceiveTimeType(context, - controller.searchController.emailReceiveTimeType.value, - (receiveTime) => controller.selectReceiveTimeQuickSearchFilter(receiveTime))); + controller.openPopupMenuAction( + context, + position, + popupMenuEmailReceiveTimeType( + context, + controller.searchController.receiveTimeFiltered, + onCallBack: (receiveTime) => controller.selectReceiveTimeQuickSearchFilter(context, receiveTime) + ) + ); } }, borderRadius: const BorderRadius.all(Radius.circular(10)), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: filter.getBackgroundColor(quickSearchFilterSelected: quickSearchFilterSelected)), + color: filter.getBackgroundColor(isFilterSelected: isFilterSelected)), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( - filter.getIcon( - imagePaths, - quickSearchFilterSelected: quickSearchFilterSelected), + filter.getIcon(imagePaths, isFilterSelected: isFilterSelected), width: 16, height: 16, fit: BoxFit.fill), const SizedBox(width: 4), Text( filter.getTitle( - context, - receiveTimeType: controller.searchController.emailReceiveTimeType.value), + context, + receiveTimeType: controller.searchController.receiveTimeFiltered, + startDate: controller.searchController.startDateFiltered, + endDate: controller.searchController.endDateFiltered + ), maxLines: 1, overflow: TextOverflow.ellipsis, - style: filter.getTextStyle( - quickSearchFilterSelected: quickSearchFilterSelected), + style: filter.getTextStyle(isFilterSelected: isFilterSelected), ), if (filter == QuickSearchFilter.last7Days) ... [ @@ -609,55 +616,21 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } List popupMenuEmailReceiveTimeType( - BuildContext context, - EmailReceiveTimeType? receiveTimeSelected, - Function(EmailReceiveTimeType?)? onCallBack + BuildContext context, + EmailReceiveTimeType? receiveTimeSelected, + {Function(EmailReceiveTimeType)? onCallBack} ) { return EmailReceiveTimeType.values - .map((timeType) => PopupMenuItem( - padding: EdgeInsets.zero, - child: _receiveTimeTileAction( - context, - receiveTimeSelected, - timeType, - onCallBack))) - .toList(); - } - - Widget _receiveTimeTileAction( - BuildContext context, - EmailReceiveTimeType? receiveTimeSelected, - EmailReceiveTimeType receiveTimeType, - Function(EmailReceiveTimeType?)? onCallBack - ) { - return InkWell( - onTap: () => onCallBack?.call(receiveTimeType == receiveTimeSelected - ? null - : receiveTimeType), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - child: SizedBox( - width: 320, - child: Row(children: [ - Expanded(child: Text( - receiveTimeType.getTitle(context), - style: const TextStyle( - fontSize: 17, - color: Colors.black, - fontWeight: FontWeight.normal))), - if (receiveTimeType == receiveTimeSelected) - ...[ - const SizedBox(width: 12), - SvgPicture.asset( - imagePaths.icFilterSelected, - width: 24, - height: 24, - fit: BoxFit.fill), - ] - ]) - ), - ) - ); + .map((receiveTime) => PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupItemNoIconWidget( + receiveTime.getTitle(context), + svgIconSelected: imagePaths.icFilterSelected, + maxWidth: 320, + isSelected: receiveTimeSelected == receiveTime, + onCallbackAction: () => onCallBack?.call(receiveTime), + ))) + .toList(); } bool supportEmptyTrash(BuildContext context) { diff --git a/lib/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.dart b/lib/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.dart index d8d07e011..f03c202c8 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.dart @@ -43,7 +43,7 @@ enum AdvancedSearchFilterField { case AdvancedSearchFilterField.notKeyword: return AppLocalizations.of(context).enterSearchTerm; case AdvancedSearchFilterField.mailBox: - return AppLocalizations.of(context).allMails; + return AppLocalizations.of(context).allMailboxes; case AdvancedSearchFilterField.date: return AppLocalizations.of(context).allTime; case AdvancedSearchFilterField.hasAttachment: diff --git a/lib/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart b/lib/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart index 2add052b4..b54d7bcf6 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart @@ -37,10 +37,8 @@ enum EmailReceiveTimeType { } } - UTCDate? toUTCDate() { + UTCDate? toOldestUTCDate() { switch(this) { - case EmailReceiveTimeType.allTime: - return null; case EmailReceiveTimeType.last7Days: final today = DateTime.now(); final last7Days = today.subtract(const Duration(days: 7)); @@ -57,8 +55,49 @@ enum EmailReceiveTimeType { final today = DateTime.now(); final lastYear = DateTime(today.year - 1, today.month, today.day); return lastYear.toUTCDate(); - case EmailReceiveTimeType.customRange: + default: return null; } } + + UTCDate? toLatestUTCDate() { + switch(this) { + case EmailReceiveTimeType.last7Days: + case EmailReceiveTimeType.last30Days: + case EmailReceiveTimeType.last6Months: + case EmailReceiveTimeType.lastYear: + return DateTime.now().toUTCDate(); + default: + return null; + } + } + + UTCDate? getAfterDate(UTCDate? startDate) { + if (startDate != null) { + return startDate; + } else { + return toOldestUTCDate(); + } + } + + UTCDate? getBeforeDate(UTCDate? endDate, UTCDate? loadMoreDate) { + if (endDate != null) { + if (loadMoreDate != null && loadMoreDate.value.isBefore(endDate.value)) { + return loadMoreDate; + } else { + return endDate; + } + } else { + final latestDate = toLatestUTCDate(); + if (latestDate != null) { + if (loadMoreDate != null && loadMoreDate.value.isBefore(latestDate.value)) { + return loadMoreDate; + } else { + return latestDate; + } + } else { + return loadMoreDate; + } + } + } } diff --git a/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart b/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart index 7d05bc781..f4a56cc2c 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart @@ -1,7 +1,10 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/cupertino.dart'; +import 'package:model/user/user_profile.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/search_email_filter.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; enum QuickSearchFilter { @@ -9,22 +12,36 @@ enum QuickSearchFilter { last7Days, fromMe; - String getTitle(BuildContext context, {EmailReceiveTimeType? receiveTimeType}) { + String getName(BuildContext context) { switch(this) { case QuickSearchFilter.hasAttachment: return AppLocalizations.of(context).hasAttachment; case QuickSearchFilter.last7Days: - if (receiveTimeType != null) { - return receiveTimeType.getTitle(context); - } return AppLocalizations.of(context).last7Days; case QuickSearchFilter.fromMe: return AppLocalizations.of(context).fromMe; } } - String getIcon(ImagePaths imagePaths, {required bool quickSearchFilterSelected}) { - if (quickSearchFilterSelected) { + String getTitle( + BuildContext context, { + EmailReceiveTimeType? receiveTimeType, + DateTime? startDate, + DateTime? endDate, + }) { + switch(this) { + case QuickSearchFilter.hasAttachment: + return AppLocalizations.of(context).hasAttachment; + case QuickSearchFilter.last7Days: + return receiveTimeType?.getTitle(context, startDate: startDate, endDate: endDate) + ?? AppLocalizations.of(context).allTime; + case QuickSearchFilter.fromMe: + return AppLocalizations.of(context).fromMe; + } + } + + String getIcon(ImagePaths imagePaths, {required bool isFilterSelected}) { + if (isFilterSelected) { return imagePaths.icSelectedSB; } else { switch(this) { @@ -38,16 +55,16 @@ enum QuickSearchFilter { } } - Color getBackgroundColor({required bool quickSearchFilterSelected}) { - if (quickSearchFilterSelected) { + Color getBackgroundColor({required bool isFilterSelected}) { + if (isFilterSelected) { return AppColor.colorItemEmailSelectedDesktop; } else { return AppColor.colorButtonHeaderThread; } } - TextStyle getTextStyle({required bool quickSearchFilterSelected}) { - if (quickSearchFilterSelected) { + TextStyle getTextStyle({required bool isFilterSelected}) { + if (isFilterSelected) { return const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, @@ -59,4 +76,19 @@ enum QuickSearchFilter { color: AppColor.colorTextButtonHeaderThread); } } + + bool isApplied(List listFilter) => listFilter.contains(this); + + bool isSelected(SearchEmailFilter filter, UserProfile? userProfile) { + switch (this) { + case QuickSearchFilter.hasAttachment: + return filter.hasAttachment == true; + case QuickSearchFilter.last7Days: + return true; + case QuickSearchFilter.fromMe: + return userProfile != null && + filter.from.contains(userProfile.email) && + filter.from.length == 1; + } + } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart b/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart index 38704a4ab..437798f42 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart @@ -5,7 +5,8 @@ import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart'; import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; -import 'package:model/model.dart'; +import 'package:model/extensions/email_filter_condition_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; @@ -52,9 +53,9 @@ class SearchEmailFilter with EquatableMixin { PresentationMailbox? mailbox, EmailReceiveTimeType? emailReceiveTimeType, bool? hasAttachment, - UTCDate? before, - UTCDate? startDate, - UTCDate? endDate, + Option? beforeOption, + Option? startDateOption, + Option? endDateOption, }) { return SearchEmailFilter( from: from ?? this.from, @@ -65,9 +66,9 @@ class SearchEmailFilter with EquatableMixin { mailbox: mailbox ?? this.mailbox, emailReceiveTimeType: emailReceiveTimeType ?? this.emailReceiveTimeType, hasAttachment: hasAttachment ?? this.hasAttachment, - before: before ?? this.before, - startDate: startDate ?? this.startDate, - endDate: endDate ?? this.endDate, + before: _getOptionParam(beforeOption, before), + startDate: _getOptionParam(startDateOption, startDate), + endDate: _getOptionParam(endDateOption, endDate), ); } @@ -84,17 +85,13 @@ class SearchEmailFilter with EquatableMixin { text: text?.value.trim().isNotEmpty == true ? text?.value : null, - inMailbox: mailbox == PresentationMailbox.unifiedMailbox - ? null - : mailbox?.id, - after: emailReceiveTimeType == EmailReceiveTimeType.customRange - ? startDate - : emailReceiveTimeType.toUTCDate(), + inMailbox: mailbox?.mailboxId, + after: emailReceiveTimeType.getAfterDate(startDate), hasAttachment: hasAttachment == false ? null : hasAttachment, - subject: subject, - before: emailReceiveTimeType == EmailReceiveTimeType.customRange - ? endDate - : before, + subject: subject?.trim().isNotEmpty == true + ? subject + : null, + before: emailReceiveTimeType.getBeforeDate(endDate, before) ); final listEmailCondition = { @@ -135,39 +132,4 @@ class SearchEmailFilter with EquatableMixin { startDate, endDate ]; -} - -extension SearchEmailFilterExtension on SearchEmailFilter { - - SearchEmailFilter clearBeforeDate() { - return SearchEmailFilter( - from: from, - to: to, - text: text, - subject: subject, - notKeyword: notKeyword, - mailbox: mailbox, - emailReceiveTimeType: emailReceiveTimeType, - hasAttachment: hasAttachment, - before: null, - startDate: startDate, - endDate: endDate, - ); - } - - SearchEmailFilter withDateRange({UTCDate? startDate, UTCDate? endDate}) { - return SearchEmailFilter( - from: from, - to: to, - text: text, - subject: subject, - notKeyword: notKeyword, - mailbox: mailbox, - emailReceiveTimeType: emailReceiveTimeType, - hasAttachment: hasAttachment, - before: before, - startDate: startDate, - endDate: endDate, - ); - } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart index 40b62b46f..27c958815 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart @@ -4,11 +4,12 @@ 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/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/advanced_search_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/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/drop_down_button_filter_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_auto_complete_email_adress.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -73,7 +74,6 @@ class AdvancedSearchInputForm extends GetWidget onTap: () => controller.selectedMailBox(context)), Row(children: [ Expanded(child: _buildFilterField( - textEditingController: controller.dateFilterInputController, context: context, advancedSearchFilterField: AdvancedSearchFilterField.date, isSelectFormList: true, @@ -102,52 +102,22 @@ class AdvancedSearchInputForm extends GetWidget List _buildEmailReceiveTimeTypeActionTiles(BuildContext context) { return EmailReceiveTimeType.values - .map( - (e) => Material( - child: PopupMenuItem( - child: Row(children: [ - const SizedBox(width: 12), - Expanded( - child: Text( - e.getTitle( - context, - startDate: controller.startDate, - endDate: controller.endDate), - style: const TextStyle( - fontSize: 15, - color: Colors.black, - fontWeight: FontWeight.w500))), - if (e == controller.dateFilterSelectedFormAdvancedSearch.value) - ...[ - const SizedBox(width: 12), - SvgPicture.asset( - _imagePaths.icFilterSelected, - width: 16, - height: 16, - fit: BoxFit.fill, - ), - ] - ]), - onTap: () { - if (e != EmailReceiveTimeType.customRange) { - controller.clearDateRangeOfFilter(); - } - controller.dateFilterSelectedFormAdvancedSearch.value = e; - controller.dateFilterInputController.text = e.getTitle( - context, - startDate: controller.startDate, - endDate: controller.endDate); - }, - ), - ), - ) - .toList(); + .map((receiveTime) => PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupItemNoIconWidget( + receiveTime.getTitle(context), + svgIconSelected: _imagePaths.icFilterSelected, + maxWidth: 320, + isSelected: controller.dateFilterSelectedFormAdvancedSearch.value == receiveTime, + onCallbackAction: () => controller.updateReceiveDateSearchFilter(context, receiveTime), + ))) + .toList(); } Widget _buildFilterField({ required BuildContext context, required AdvancedSearchFilterField advancedSearchFilterField, - required TextEditingController textEditingController, + TextEditingController? textEditingController, VoidCallback? onTap, bool isSelectFormList = false, MouseCursor? mouseCursor, @@ -180,7 +150,13 @@ class AdvancedSearchInputForm extends GetWidget ) else if (_responsiveUtils.landscapeTabletSupported(context)) if (advancedSearchFilterField == AdvancedSearchFilterField.date) - const DateDropDownButton() + Obx(() => DateDropDownButton( + _imagePaths, + startDate: controller.startDate.value, + endDate: controller.endDate.value, + receiveTimeSelected: controller.dateFilterSelectedFormAdvancedSearch.value, + onReceiveTimeSelected: (receiveTime) => controller.updateReceiveDateSearchFilter(context, receiveTime), + )) else _buildTextField( isSelectFormList: isSelectFormList, @@ -222,7 +198,7 @@ class AdvancedSearchInputForm extends GetWidget Widget _buildTextFieldFilterForWeb({ required BuildContext context, required AdvancedSearchFilterField advancedSearchFilterField, - required TextEditingController textEditingController, + TextEditingController? textEditingController, VoidCallback? onTap, bool isSelectFormList = false, MouseCursor? mouseCursor, @@ -231,7 +207,13 @@ class AdvancedSearchInputForm extends GetWidget }) { switch (advancedSearchFilterField) { case AdvancedSearchFilterField.date: - return const DateDropDownButton(); + return Obx(() => DateDropDownButton( + _imagePaths, + startDate: controller.startDate.value, + endDate: controller.endDate.value, + receiveTimeSelected: controller.dateFilterSelectedFormAdvancedSearch.value, + onReceiveTimeSelected: (receiveTime) => controller.updateReceiveDateSearchFilter(context, receiveTime), + )); default: return _buildTextField( isSelectFormList: isSelectFormList, @@ -357,7 +339,7 @@ class AdvancedSearchInputForm extends GetWidget Widget _buildTextField({ required BuildContext context, required AdvancedSearchFilterField advancedSearchFilterField, - required TextEditingController textEditingController, + TextEditingController? textEditingController, VoidCallback? onTap, bool isSelectFormList = false, MouseCursor? mouseCursor, diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart new file mode 100644 index 000000000..f711b23e8 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart @@ -0,0 +1,130 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +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'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; + +class DateDropDownButton extends StatelessWidget { + + final ImagePaths imagePaths; + final DateTime? startDate; + final DateTime? endDate; + final EmailReceiveTimeType? receiveTimeSelected; + final Function(EmailReceiveTimeType)? onReceiveTimeSelected; + + const DateDropDownButton( + this.imagePaths, { + Key? key, + this.startDate, + this.endDate, + this.receiveTimeSelected, + this.onReceiveTimeSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return DropdownButtonHideUnderline( + child: PointerInterceptor( + child: DropdownButton2( + isExpanded: true, + items: EmailReceiveTimeType.values + .map((item) => _buildItemMenu(context, item)) + .toList(), + value: receiveTimeSelected, + customButton: Container( + height: 44, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: AppColor.colorInputBorderCreateMailbox, + width: 1, + ), + color: AppColor.colorInputBackgroundCreateMailbox + ), + padding: const EdgeInsets.only(left: 12, right: 10), + child: Row(children: [ + Expanded(child: Text( + receiveTimeSelected?.getTitle(context, startDate: startDate, endDate: endDate) ?? '', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + SvgPicture.asset(imagePaths.icDropDown) + ]), + ), + onChanged: (value) { + if (value != null) { + onReceiveTimeSelected?.call(value); + } + }, + icon: SvgPicture.asset(imagePaths.icDropDown), + buttonPadding: const EdgeInsets.symmetric(horizontal: 12), + buttonDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: AppColor.colorInputBorderCreateMailbox, + width: 1, + ), + color: AppColor.colorInputBackgroundCreateMailbox, + ), + itemHeight: 44, + buttonHeight: 44, + selectedItemHighlightColor: Colors.white, + itemPadding: const EdgeInsets.symmetric(horizontal: 12), + dropdownMaxHeight: 200, + dropdownDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + ), + offset: const Offset(0.0, -8.0), + dropdownElevation: 4, + scrollbarRadius: const Radius.circular(40), + scrollbarThickness: 6 + ), + ), + ); + } + + DropdownMenuItem _buildItemMenu( + BuildContext context, + EmailReceiveTimeType receiveTime + ) { + return DropdownMenuItem( + value: receiveTime, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: 44, + child: Row(children: [ + Expanded(child: Text( + receiveTime.getTitle(context), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + if (receiveTime == receiveTimeSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: 20, + height: 20, + fit: BoxFit.fill + ) + ]), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart index 91f2a0924..6495f409c 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -34,9 +35,8 @@ class IconOpenAdvancedSearchWidget extends StatelessWidget { width: 16, height: 16), onTap: () { - if(searchController.isAdvancedSearchViewOpen.isFalse && searchController.advancedSearchIsActivated.isFalse){ - advancedFilterController.initSearchFilterField(context); - } + log('IconOpenAdvancedSearchWidget::build(): clicked'); + advancedFilterController.initSearchFilterField(context); searchController.showAdvancedFilterView(_parentContext); }), ), diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart index ca843498a..0057af269 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart @@ -128,10 +128,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { }, buttonActionCallback: (filterAction) { if (filterAction is QuickSearchFilter) { - dashBoardController.selectQuickSearchFilter( - quickSearchFilter: filterAction, - fromSuggestionBox: true, - ); + dashBoardController.addFilterToSuggestionForm(filterAction); } }, listActionPadding: const EdgeInsets.only( @@ -210,43 +207,31 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { } Widget buildListButtonForQuickSearchForm(BuildContext context, QuickSearchFilter filter) { - final controller = dashBoardController.searchController; - return Obx(() { - final isFilterSelected = dashBoardController.checkQuickSearchFilterSelected( - quickSearchFilter: filter); + final isFilterSelected = filter.isApplied(dashBoardController.searchController.listFilterOnSuggestionForm); return Chip( labelPadding: const EdgeInsets.only( - top: 2, - bottom: 2, - right: 10), + top: 2, + bottom: 2, + right: 10), label: Text( - filter.getTitle( - context, - receiveTimeType: controller.emailReceiveTimeType.value), + filter.getName(context), maxLines: 1, overflow: CommonTextStyle.defaultTextOverFlow, softWrap: CommonTextStyle.defaultSoftWrap, - style: filter.getTextStyle( - quickSearchFilterSelected: isFilterSelected), + style: filter.getTextStyle(isFilterSelected: isFilterSelected), ), avatar: SvgPicture.asset( - filter.getIcon( - imagePaths, - quickSearchFilterSelected: isFilterSelected), - width: 16, - height: 16, - fit: BoxFit.fill), - labelStyle: filter.getTextStyle( - quickSearchFilterSelected: isFilterSelected), - backgroundColor: filter.getBackgroundColor( - quickSearchFilterSelected: isFilterSelected), + filter.getIcon(imagePaths, isFilterSelected: isFilterSelected), + width: 16, + height: 16, + fit: BoxFit.fill), + labelStyle: filter.getTextStyle(isFilterSelected: isFilterSelected), + backgroundColor: filter.getBackgroundColor(isFilterSelected: isFilterSelected), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), - side: BorderSide( - color: filter.getBackgroundColor( - quickSearchFilterSelected: isFilterSelected)), + side: BorderSide(color: filter.getBackgroundColor(isFilterSelected: isFilterSelected)), ), ); }); diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 027a3ea3c..9e4e47c94 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -151,6 +151,7 @@ class ThreadController extends BaseController with EmailActionController { newState.fold( (failure) { if (failure is SearchEmailFailure) { + canSearchMore = false; mailboxDashBoardController.emailsInCurrentMailbox.clear(); } else if (failure is SearchMoreEmailFailure || failure is LoadMoreEmailsFailure) { _isLoadingMore = false; @@ -197,7 +198,7 @@ class ThreadController extends BaseController with EmailActionController { if (_currentMailboxId != mailbox.id) { _currentMailboxId = mailbox.id; _resetToOriginalValue(); - _getAllEmail(); + _getAllEmailAction(); } } else if (mailbox == null) { // disable current mailbox when search active _currentMailboxId = null; @@ -262,6 +263,12 @@ class ThreadController extends BaseController with EmailActionController { _navigationRouter = action.navigationRouter; _activateSearchFromRouter(); mailboxDashBoardController.clearDashBoardAction(); + } else if (action is SelectDateRangeToAdvancedSearch || action is ClearDateRangeToAdvancedSearch) { + if (listEmailController.hasClients) { + listEmailController.jumpTo(0); + } + canSearchMore = true; + mailboxDashBoardController.emailsInCurrentMailbox.clear(); } }); @@ -335,18 +342,12 @@ class ThreadController extends BaseController with EmailActionController { logError('ThreadController::_handleErrorGetAllOrRefreshChangesEmail():Error: $error'); if (error is CannotCalculateChangesMethodResponseException) { await _cachingManager.clearEmailCache(); - _getAllEmail(); + _getAllEmailAction(); } else { super.onError(error); } } - void _getAllEmail() { - if (_accountId != null) { - _getAllEmailAction(_accountId!, mailboxId: _currentMailboxId); - } - } - void _resetToOriginalValue() { dispatchState(Right(LoadingState())); mailboxDashBoardController.emailsInCurrentMailbox.clear(); @@ -389,48 +390,47 @@ class ThreadController extends BaseController with EmailActionController { } } - void _getAllEmailAction(AccountId accountId, {MailboxId? mailboxId}) { - consumeState(_getEmailsInMailboxInteractor.execute( - accountId, - limit: ThreadConstants.defaultLimit, - sort: _sortOrder, - emailFilter: EmailFilter( - filter: _getFilterCondition(), - filterOption: mailboxDashBoardController.filterMessageOption.value, - mailboxId: mailboxId ?? _currentMailboxId), - propertiesCreated: ThreadConstants.propertiesDefault, - propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - )); + void _getAllEmailAction() { + if (_accountId != null) { + consumeState(_getEmailsInMailboxInteractor.execute( + _accountId!, + limit: ThreadConstants.defaultLimit, + sort: _sortOrder, + emailFilter: EmailFilter( + filter: _getFilterCondition(mailboxIdSelected: _currentMailboxId), + filterOption: mailboxDashBoardController.filterMessageOption.value, + mailboxId: _currentMailboxId + ), + propertiesCreated: ThreadConstants.propertiesDefault, + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, + )); + } } - EmailFilterCondition _getFilterCondition({bool isLoadMore = false}) { - final lastEmail = mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty - ? mailboxDashBoardController.emailsInCurrentMailbox.last - : null; - final mailboxIdSelected = mailboxDashBoardController.selectedMailbox.value?.id; + EmailFilterCondition _getFilterCondition({PresentationEmail? oldestEmail, MailboxId? mailboxIdSelected}) { switch(mailboxDashBoardController.filterMessageOption.value) { case FilterMessageOption.all: return EmailFilterCondition( inMailbox: mailboxIdSelected, - before: isLoadMore ? lastEmail?.receivedAt : null + before: oldestEmail?.receivedAt ); case FilterMessageOption.unread: return EmailFilterCondition( inMailbox: mailboxIdSelected, notKeyword: KeyWordIdentifier.emailSeen.value, - before: isLoadMore ? lastEmail?.receivedAt : null + before: oldestEmail?.receivedAt ); case FilterMessageOption.attachments: return EmailFilterCondition( inMailbox: mailboxIdSelected, hasAttachment: true, - before: isLoadMore ? lastEmail?.receivedAt : null + before: oldestEmail?.receivedAt ); case FilterMessageOption.starred: return EmailFilterCondition( inMailbox: mailboxIdSelected, hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: isLoadMore ? lastEmail?.receivedAt : null + before: oldestEmail?.receivedAt ); } } @@ -441,10 +441,9 @@ class ThreadController extends BaseController with EmailActionController { cancelSelectEmail(); if (searchController.isSearchEmailRunning) { - searchController.searchEmailFilter.value = _searchEmailFilter.clearBeforeDate(); _searchEmail(limit: limitEmailFetched); } else { - _getAllEmail(); + _getAllEmailAction(); } } @@ -459,22 +458,22 @@ class ThreadController extends BaseController with EmailActionController { void _refreshEmailChanges({jmap.State? currentEmailState}) { log('ThreadController::_refreshEmailChanges(): currentEmailState: $currentEmailState'); if (searchController.isSearchEmailRunning) { - searchController.searchEmailFilter.value = _searchEmailFilter.clearBeforeDate(); _searchEmail(limit: limitEmailFetched); } else { final newEmailState = currentEmailState ?? _currentEmailState; log('ThreadController::_refreshEmailChanges(): newEmailState: $newEmailState'); if (_accountId != null && newEmailState != null) { consumeState(_refreshChangesEmailsInMailboxInteractor.execute( - _accountId!, - newEmailState, - sort: _sortOrder, - propertiesCreated: ThreadConstants.propertiesDefault, - propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - emailFilter: EmailFilter( - filter: _getFilterCondition(), - filterOption: mailboxDashBoardController.filterMessageOption.value, - mailboxId: _currentMailboxId), + _accountId!, + newEmailState, + sort: _sortOrder, + propertiesCreated: ThreadConstants.propertiesDefault, + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, + emailFilter: EmailFilter( + filter: _getFilterCondition(mailboxIdSelected: _currentMailboxId), + filterOption: mailboxDashBoardController.filterMessageOption.value, + mailboxId: _currentMailboxId + ) )); } } @@ -483,15 +482,19 @@ class ThreadController extends BaseController with EmailActionController { void loadMoreEmails() { log('ThreadController::loadMoreEmails()'); if (canLoadMore && _accountId != null) { + final oldestEmail = mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty + ? mailboxDashBoardController.emailsInCurrentMailbox.last + : null; consumeState(_loadMoreEmailsInMailboxInteractor.execute( GetEmailRequest( - _accountId!, - limit: ThreadConstants.defaultLimit, - sort: _sortOrder, - filterOption: mailboxDashBoardController.filterMessageOption.value, - filter: _getFilterCondition(isLoadMore: true), - properties: ThreadConstants.propertiesDefault, - lastEmailId: mailboxDashBoardController.emailsInCurrentMailbox.last.id) + _accountId!, + limit: ThreadConstants.defaultLimit, + sort: _sortOrder, + filterOption: mailboxDashBoardController.filterMessageOption.value, + filter: _getFilterCondition(oldestEmail: oldestEmail), + properties: ThreadConstants.propertiesDefault, + lastEmailId: oldestEmail?.id + ) )); } } @@ -649,7 +652,7 @@ class ThreadController extends BaseController with EmailActionController { icon: newFilterOption.getIconToast(_imagePaths)); if (searchController.isSearchEmailRunning) { - _searchEmail(filterCondition: _getFilterCondition()); + _searchEmail(); } else { refreshAllEmail(); } @@ -664,21 +667,22 @@ class ThreadController extends BaseController with EmailActionController { searchController.clearTextSearch(); } - void _searchEmail({UnsignedInt? limit, EmailFilterCondition? filterCondition}) { + void _searchEmail({UnsignedInt? limit}) { if (_accountId != null) { - searchController.activateSimpleSearch(); + if (listEmailController.hasClients) { + listEmailController.jumpTo(0); + } + mailboxDashBoardController.emailsInCurrentMailbox.clear(); + canSearchMore = true; + searchController.updateFilterEmail(beforeOption: const None()); - filterCondition = EmailFilterCondition( - notKeyword: filterCondition?.notKeyword, - hasKeyword: filterCondition?.hasKeyword, - hasAttachment: filterCondition?.hasAttachment, - ); + searchController.activateSimpleSearch(); consumeState(_searchEmailInteractor.execute( _accountId!, limit: limit ?? ThreadConstants.defaultLimit, sort: _sortOrder, - filter: _searchEmailFilter.mappingToEmailFilterCondition(moreFilterCondition: filterCondition), + filter: _searchEmailFilter.mappingToEmailFilterCondition(moreFilterCondition: _getFilterCondition()), properties: ThreadConstants.propertiesDefault, )); } @@ -697,6 +701,7 @@ class ThreadController extends BaseController with EmailActionController { } void _searchEmailsSuccess(SearchEmailSuccess success) { + canSearchMore = true; final resultEmailSearchList = success.emailList .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .toList(); @@ -716,21 +721,24 @@ class ThreadController extends BaseController with EmailActionController { void searchMoreEmails() { if (canSearchMore && _accountId != null) { - final lastEmail = mailboxDashBoardController.emailsInCurrentMailbox.last; - searchController.updateFilterEmail(before: lastEmail.receivedAt); + final oldestEmail = mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty + ? mailboxDashBoardController.emailsInCurrentMailbox.last + : null; + searchController.updateFilterEmail(beforeOption: optionOf(oldestEmail?.receivedAt)); consumeState(_searchMoreEmailInteractor.execute( _accountId!, limit: ThreadConstants.defaultLimit, sort: _sortOrder, - filter: searchController.searchEmailFilter.value.mappingToEmailFilterCondition(), + filter: searchController.searchEmailFilter.value.mappingToEmailFilterCondition(moreFilterCondition: _getFilterCondition()), properties: ThreadConstants.propertiesDefault, - lastEmailId: lastEmail.id + lastEmailId: oldestEmail?.id )); } } void _searchMoreEmailsSuccess(SearchMoreEmailSuccess success) { if (success.emailList.isNotEmpty) { + canSearchMore = true; final resultEmailSearchList = success.emailList .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .where((email) => !mailboxDashBoardController.emailsInCurrentMailbox.contains(email)) diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index dc220dd72..931c394a5 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2023-02-24T11:57:26.810965", + "@@last_modified": "2023-03-02T21:08:14.879842", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -1488,12 +1488,6 @@ "placeholders_order": [], "placeholders": {} }, - "allMails": "All mails", - "@allMails": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "allTime": "All time", "@allTime": { "type": "text", diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 9b4e9c70e..ad61ff18d 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1515,13 +1515,6 @@ class AppLocalizations { ); } - String get allMails { - return Intl.message( - 'All mails', - name: 'allMails', - ); - } - String get allTime { return Intl.message( 'All time', diff --git a/model/lib/mailbox/presentation_mailbox.dart b/model/lib/mailbox/presentation_mailbox.dart index ae462c9ab..8f7fc3ff6 100644 --- a/model/lib/mailbox/presentation_mailbox.dart +++ b/model/lib/mailbox/presentation_mailbox.dart @@ -107,6 +107,14 @@ class PresentationMailbox with EquatableMixin { bool get allowedToDisplay => isSubscribedMailbox || isDefault; + MailboxId? get mailboxId { + if (id == unifiedMailbox.id) { + return null; + } else { + return id; + } + } + @override List get props => [ id,