TF-997 Custom date ranges in search

This commit is contained in:
dab246
2022-10-05 22:59:44 +07:00
committed by Dat H. Pham
parent a363435de2
commit 796a0fdba7
11 changed files with 369 additions and 32 deletions
@@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:core/core.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';
@@ -19,7 +20,9 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -45,12 +48,20 @@ class AdvancedFilterController extends BaseController {
final SearchController searchController = Get.find<SearchController>();
final MailboxDashBoardController _mailboxDashBoardController =
Get.find<MailboxDashBoardController>();
final _appToast = Get.find<AppToast>();
final _imagePaths = Get.find<ImagePaths>();
SearchEmailFilter get searchEmailFilter =>
searchController.searchEmailFilter.value;
final focusManager = InputFieldFocusManager.initial();
DateTime? _startDate, _endDate;
DateTime? get startDate => _startDate;
DateTime? get endDate => _endDate;
@override
void onReady() {
if (!BuildUtils.isWeb) {
@@ -71,6 +82,7 @@ class AdvancedFilterController extends BaseController {
void cleanSearchFilter(BuildContext context) {
searchController.cleanSearchFilter();
dateFilterSelectedFormAdvancedSearch.value = EmailReceiveTimeType.allTime;
clearDateRangeOfFilter();
subjectFilterInputController.text = '';
hasKeyWordFilterInputController.text = '';
notKeyWordFilterInputController.text = '';
@@ -117,6 +129,8 @@ class AdvancedFilterController extends BaseController {
StringConvert.writeEmptyToNull(subjectFilterInputController.text),
emailReceiveTimeType: dateFilterSelectedFormAdvancedSearch.value,
hasAttachment: hasAttachment.value,
endDate: _endDate.toUTCDate(),
startDate: _startDate.toUTCDate()
);
}
@@ -227,7 +241,10 @@ class AdvancedFilterController extends BaseController {
notKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(
searchEmailFilter.notKeyword.firstOrNull);
dateFilterInputController.text = StringConvert.writeNullToEmpty(
searchEmailFilter.emailReceiveTimeType.getTitle(context));
searchEmailFilter.emailReceiveTimeType.getTitle(
context,
startDate: _startDate,
endDate: _endDate));
mailBoxFilterInputController.text =
StringConvert.writeNullToEmpty(searchEmailFilter.mailbox?.name?.name);
dateFilterSelectedFormAdvancedSearch.value =
@@ -235,6 +252,91 @@ class AdvancedFilterController extends BaseController {
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: 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);
},
)
);
}
);
}
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 clearDateRangeOfFilter() {
_startDate = null;
_endDate = null;
searchController.searchEmailFilter.value =
searchController.searchEmailFilter.value.withDateRange(
startDate: _startDate.toUTCDate(),
endDate: _endDate.toUTCDate());
}
@override
void onClose() {
focusManager.dispose();
@@ -129,6 +129,8 @@ class SearchController extends BaseController {
EmailReceiveTimeType? emailReceiveTimeType,
bool? hasAttachment,
UTCDate? before,
UTCDate? startDate,
UTCDate? endDate,
}) {
searchEmailFilter.value = searchEmailFilter.value.copyWith(
from: from,
@@ -140,6 +142,8 @@ class SearchController extends BaseController {
emailReceiveTimeType: emailReceiveTimeType,
hasAttachment: hasAttachment,
before: before,
startDate: startDate,
endDate: endDate,
);
}
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
enum EmailReceiveTimeType {
@@ -8,9 +9,10 @@ enum EmailReceiveTimeType {
last7Days,
last30Days,
last6Months,
lastYear;
lastYear,
customRange;
String getTitle(BuildContext context) {
String getTitle(BuildContext context, {DateTime? startDate, DateTime? endDate}) {
switch(this) {
case EmailReceiveTimeType.allTime:
return AppLocalizations.of(context).allTime;
@@ -22,6 +24,16 @@ enum EmailReceiveTimeType {
return AppLocalizations.of(context).last6Months;
case EmailReceiveTimeType.lastYear:
return AppLocalizations.of(context).lastYears;
case EmailReceiveTimeType.customRange:
if (startDate != null && endDate != null) {
final startDateString = startDate.formatDate(pattern: 'yyyy-dd-MM');
final endDateString = endDate.formatDate(pattern: 'yyyy-dd-MM');
return AppLocalizations.of(context).dateRangeAdvancedSearchFilter(
startDateString,
endDateString);
} else {
return AppLocalizations.of(context).customRange;
}
}
}
@@ -30,13 +42,23 @@ enum EmailReceiveTimeType {
case EmailReceiveTimeType.allTime:
return null;
case EmailReceiveTimeType.last7Days:
return UTCDate(DateTime.now().subtract(const Duration(days: 7)));
final today = DateTime.now();
final last7Days = today.subtract(const Duration(days: 7));
return last7Days.toUTCDate();
case EmailReceiveTimeType.last30Days:
return UTCDate(DateTime.now().subtract(const Duration(days: 30)));
final today = DateTime.now();
final last30Days = today.subtract(const Duration(days: 30));
return last30Days.toUTCDate();
case EmailReceiveTimeType.last6Months:
return UTCDate(DateTime.now().subtract(const Duration(days: 180)));
final today = DateTime.now();
final last6months = DateTime(today.year, today.month - 6, today.day);
return last6months.toUTCDate();
case EmailReceiveTimeType.lastYear:
return UTCDate(DateTime.now().subtract(const Duration(days: 365)));
final today = DateTime.now();
final lastYear = DateTime(today.year - 1, today.month, today.day);
return lastYear.toUTCDate();
case EmailReceiveTimeType.customRange:
return null;
}
}
}
@@ -17,6 +17,8 @@ class SearchEmailFilter {
final EmailReceiveTimeType emailReceiveTimeType;
final bool hasAttachment;
final UTCDate? before;
final UTCDate? startDate;
final UTCDate? endDate;
SearchEmailFilter({
Set<String>? from,
@@ -28,6 +30,8 @@ class SearchEmailFilter {
Set<String>? notKeyword,
this.mailbox,
this.before,
this.startDate,
this.endDate,
}) : from = from ?? <String>{},
to = to ?? <String>{},
notKeyword = notKeyword ?? <String>{},
@@ -45,6 +49,8 @@ class SearchEmailFilter {
EmailReceiveTimeType? emailReceiveTimeType,
bool? hasAttachment,
UTCDate? before,
UTCDate? startDate,
UTCDate? endDate,
}) {
return SearchEmailFilter(
from: from ?? this.from,
@@ -56,6 +62,8 @@ class SearchEmailFilter {
emailReceiveTimeType: emailReceiveTimeType ?? this.emailReceiveTimeType,
hasAttachment: hasAttachment ?? this.hasAttachment,
before: before ?? this.before,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
);
}
@@ -67,10 +75,14 @@ class SearchEmailFilter {
inMailbox: mailbox == PresentationMailbox.unifiedMailbox
? null
: mailbox?.id,
after: emailReceiveTimeType.toUTCDate(),
after: emailReceiveTimeType == EmailReceiveTimeType.customRange
? startDate
: emailReceiveTimeType.toUTCDate(),
hasAttachment: hasAttachment == false ? null : hasAttachment,
subject: subject,
before: before,
before: emailReceiveTimeType == EmailReceiveTimeType.customRange
? endDate
: before,
);
final listEmailCondition = {
@@ -113,4 +125,20 @@ extension SearchEmailFilterExtension on SearchEmailFilter {
before: newBefore,
);
}
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,
);
}
}
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear
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/text_field_auto_complete_email_adress.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
with PopupContextMenuActionMixin {
@@ -70,18 +71,30 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode,
mouseCursor: SystemMouseCursors.click,
onTap: () => controller.selectedMailBox()),
_buildFilterField(
textEditingController: controller.dateFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.date,
isSelectFormList: true,
onTap: () {
openContextMenuAction(
context,
_buildEmailReceiveTimeTypeActionTiles(context),
);
},
),
Row(children: [
Expanded(child: _buildFilterField(
textEditingController: controller.dateFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.date,
isSelectFormList: true,
onTap: () {
openContextMenuAction(
context,
_buildEmailReceiveTimeTypeActionTiles(context),
);
},
)),
const SizedBox(width: 10),
buildIconWeb(
icon: SvgPicture.asset(
_imagePaths.icCalendarSB,
width: 24,
height: 24,
fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).selectDate,
iconPadding: EdgeInsets.zero,
onTap: () => controller.selectDateRange(context)),
]),
AdvancedSearchFilterFormBottomView(focusManager: controller.focusManager)
],
);
@@ -95,7 +108,11 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
child: Row(children: [
const SizedBox(width: 12),
Expanded(
child: Text(e.getTitle(context),
child: Text(
e.getTitle(
context,
startDate: controller.startDate,
endDate: controller.endDate),
style: const TextStyle(
fontSize: 15,
color: Colors.black,
@@ -112,8 +129,14 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
]
]),
onTap: () {
if (e != EmailReceiveTimeType.customRange) {
controller.clearDateRangeOfFilter();
}
controller.dateFilterSelectedFormAdvancedSearch.value = e;
controller.dateFilterInputController.text = e.getTitle(context);
controller.dateFilterInputController.text = e.getTitle(
context,
startDate: controller.startDate,
endDate: controller.endDate);
},
),
),
@@ -17,8 +17,17 @@ class DateDropDownButton extends GetWidget<AdvancedFilterController> {
items: EmailReceiveTimeType.values,
itemSelected: controller.dateFilterSelectedFormAdvancedSearch.value,
context: context,
startDate: controller.startDate,
endDate: controller.endDate,
onChanged: (item) {
if (item != EmailReceiveTimeType.customRange) {
controller.clearDateRangeOfFilter();
}
controller.dateFilterSelectedFormAdvancedSearch.value = item!;
controller.dateFilterInputController.text = item.getTitle(
context,
startDate: controller.startDate,
endDate: controller.endDate);
},
),
);
@@ -30,6 +39,8 @@ Widget _buildDropDownButton<T>({
required T itemSelected,
required BuildContext context,
required Function(T?)? onChanged,
DateTime? startDate,
DateTime? endDate
}) {
final ImagePaths _imagePaths = Get.find<ImagePaths>();
@@ -43,6 +54,8 @@ Widget _buildDropDownButton<T>({
StringConvert.writeNullToEmpty(_getTextItemDropdown<T>(
item: item,
context: context,
startDate: startDate,
endDate: endDate,
)),
style: const TextStyle(
fontSize: 16,
@@ -81,10 +94,13 @@ Widget _buildDropDownButton<T>({
);
}
String? _getTextItemDropdown<T>(
{required T item, required BuildContext context}) {
String? _getTextItemDropdown<T>({required T item,
required BuildContext context,
DateTime? startDate,
DateTime? endDate
}) {
if (item is EmailReceiveTimeType) {
return item.getTitle(context);
return item.getTitle(context, startDate: startDate, endDate: endDate);
}
if (item is Mailbox) {
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
extension DateTimeExtension on DateTime? {
@@ -17,6 +18,14 @@ extension DateTimeExtension on DateTime? {
}
return null;
}
UTCDate? toUTCDate() {
if (this != null) {
return UTCDate(this!.toUtc());
} else {
return null;
}
}
}
extension TimeOfDayExtension on TimeOfDay? {
+49 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2022-09-30T17:54:08.425416",
"@@last_modified": "2022-10-05T22:55:58.696295",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -2245,5 +2245,53 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"selectDate": "Select date",
"@selectDate": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"setDate": "Set date",
"@setDate": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"toastMessageErrorWhenSelectStartDateIsEmpty": "The start date cannot be null.",
"@toastMessageErrorWhenSelectStartDateIsEmpty": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"toastMessageErrorWhenSelectEndDateIsEmpty": "The end date cannot be null.",
"@toastMessageErrorWhenSelectEndDateIsEmpty": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"toastMessageErrorWhenSelectDateIsInValid": "The end time cannot be less than the start time.",
"@toastMessageErrorWhenSelectDateIsInValid": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"dateRangeAdvancedSearchFilter": "From {startDate} to {endDate}",
"@dateRangeAdvancedSearchFilter": {
"type": "text",
"placeholders_order": [
"startDate",
"endDate"
],
"placeholders": {
"startDate": {},
"endDate": {}
}
},
"customRange": "Custom range",
"@customRange": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -2327,4 +2327,54 @@ class AppLocalizations {
name: 'titleForeground'
);
}
String get selectDate {
return Intl.message(
'Select date',
name: 'selectDate',
);
}
String get setDate {
return Intl.message(
'Set date',
name: 'setDate',
);
}
String get toastMessageErrorWhenSelectStartDateIsEmpty {
return Intl.message(
'The start date cannot be null.',
name: 'toastMessageErrorWhenSelectStartDateIsEmpty',
);
}
String get toastMessageErrorWhenSelectEndDateIsEmpty {
return Intl.message(
'The end date cannot be null.',
name: 'toastMessageErrorWhenSelectEndDateIsEmpty',
);
}
String get toastMessageErrorWhenSelectDateIsInValid {
return Intl.message(
'The end time cannot be less than the start time.',
name: 'toastMessageErrorWhenSelectDateIsInValid',
);
}
String dateRangeAdvancedSearchFilter(String startDate, String endDate) {
return Intl.message(
'From $startDate to $endDate',
name: 'dateRangeAdvancedSearchFilter',
args: [startDate, endDate]
);
}
String get customRange {
return Intl.message(
'Custom range',
name: 'customRange');
}
}
+35 -5
View File
@@ -481,6 +481,15 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
flutter_date_range_picker:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: c04608f5daad5e96daf0f419946ad0db23c46811
url: "https://github.com/linagora/flutter-date-range-picker.git"
source: git
version: "1.0.0"
flutter_dotenv:
dependency: "direct main"
description:
@@ -891,7 +900,7 @@ packages:
name: package_info_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.0.6"
package_info_plus_windows:
dependency: transitive
description:
@@ -955,6 +964,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
pattern_formatter:
dependency: transitive
description:
name: pattern_formatter
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
pedantic:
dependency: transitive
description:
@@ -989,7 +1005,7 @@ packages:
name: permission_handler_apple
url: "https://pub.dartlang.org"
source: hosted
version: "9.0.4"
version: "9.0.6"
permission_handler_platform_interface:
dependency: transitive
description:
@@ -1003,7 +1019,7 @@ packages:
name: permission_handler_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
version: "0.1.1"
petitparser:
dependency: transitive
description:
@@ -1088,7 +1104,7 @@ packages:
description:
path: "."
ref: master
resolved-ref: "85fe10cd4d3b29bbf803ac796ebe722710fa1d36"
resolved-ref: "45ed9a973331ac4209917e365792967d7ca7b5bb"
url: "https://github.com/linagora/rich-text-composer.git"
source: git
version: "0.0.1"
@@ -1251,6 +1267,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1"
syncfusion_flutter_core:
dependency: transitive
description:
name: syncfusion_flutter_core
url: "https://pub.dartlang.org"
source: hosted
version: "20.3.48"
syncfusion_flutter_datepicker:
dependency: transitive
description:
name: syncfusion_flutter_datepicker
url: "https://pub.dartlang.org"
source: hosted
version: "20.3.48"
synchronized:
dependency: transitive
description:
@@ -1425,7 +1455,7 @@ packages:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.10.3"
version: "2.10.4"
webview_flutter_platform_interface:
dependency: transitive
description:
+5
View File
@@ -191,6 +191,11 @@ dependencies:
byte_converter: 1.3.0
flutter_date_range_picker:
git:
url: https://github.com/linagora/flutter-date-range-picker.git
ref: master
dev_dependencies:
flutter_test:
sdk: flutter