create advanced search mobile UI

This commit is contained in:
ManhNT
2022-06-25 23:14:29 +07:00
committed by Dat H. Pham
parent 298e0e2e8b
commit e5977aa0da
15 changed files with 496 additions and 17 deletions
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.12" d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24Z" fill="#818C99"/>
<path d="M16.7364 7.2636C17.0879 7.61508 17.0879 8.18492 16.7364 8.5364L13.273 12L16.7364 15.4636C17.0586 15.7858 17.0854 16.2915 16.817 16.6442L16.7364 16.7364C16.3849 17.0879 15.8151 17.0879 15.4636 16.7364L12 13.273L8.5364 16.7364C8.18492 17.0879 7.61508 17.0879 7.2636 16.7364C6.91213 16.3849 6.91213 15.8151 7.2636 15.4636L10.727 12L7.2636 8.5364C6.94142 8.21421 6.91457 7.70853 7.18306 7.35577L7.2636 7.2636C7.61508 6.91213 8.18492 6.91213 8.5364 7.2636L12 10.727L15.4636 7.2636C15.8151 6.91213 16.3849 6.91213 16.7364 7.2636Z" fill="#818C99"/>
</svg>

After

Width:  |  Height:  |  Size: 810 B

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+1
View File
@@ -70,6 +70,7 @@ export 'presentation/views/tab_bar/custom_tab_indicator.dart';
export 'presentation/views/quick_search/quick_search_input_form.dart';
export 'presentation/views/toast/toast_position.dart';
export 'presentation/views/toast/tmail_toast.dart';
export 'presentation/views/bottom_popup/full_screen_action_sheet_builder.dart';
// Resources
export 'presentation/resources/assets_paths.dart';
@@ -75,7 +75,7 @@ class ImagePaths {
String get icSendMobile => _getImagePath('ic_send_mobile.svg');
String get icSendDisable => _getImagePath('ic_send_disable.svg');
String get icArrowDown => _getImagePath('ic_arrow_down.svg');
String get icFilterWeb => _getImagePath('ic_filter_web.svg');
String get icFilterAdvanced => _getImagePath('ic_filter_advanced.svg');
String get icMarkAllAsRead => _getImagePath('ic_mark_all_as_read.svg');
String get icRefresh => _getImagePath('ic_refresh.svg');
String get icSelectAll => _getImagePath('ic_select_all.svg');
@@ -125,6 +125,7 @@ class ImagePaths {
String get icFilePdf => _getImagePath('ic_file_pdf.svg');
String get icFilePptx => _getImagePath('ic_file_pptx.svg');
String get icFileEPup => _getImagePath('ic_file_epup.svg');
String get icCloseAdvancedSearch => _getImagePath('ic_close_advanced_search.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
@@ -0,0 +1,72 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
typedef OnCloseActionClick = Function();
class FullScreenActionSheetBuilder {
final BuildContext context;
final Widget child;
final Widget? titleWidget;
final Widget? cancelWidget;
OnCloseActionClick? onCloseActionClick;
late double _statusBarHeight = MediaQuery.of(context).padding.top;
FullScreenActionSheetBuilder({
required this.context,
required this.child,
this.titleWidget,
this.cancelWidget,
this.onCloseActionClick,
});
Future show() {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
backgroundColor: Colors.transparent,
enableDrag: false,
builder: (context) => PointerInterceptor(child: _buildBody(context)),
);
}
Widget _buildBody(BuildContext context) {
return SafeArea(
top: true,
bottom: false,
left: false,
right: false,
child: GestureDetector(
onTap: () => {},
child: Padding(
padding: EdgeInsets.only(top: _statusBarHeight),
child: ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(14),
topLeft: Radius.circular(14),
),
child: Scaffold(
appBar: AppBar(
leading: SizedBox.shrink(),
title: titleWidget,
centerTitle: true,
actions: [
GestureDetector(
child: cancelWidget,
onTapDown: (_) {
onCloseActionClick?.call();
Get.back();
},
)
],
),
body: child,
),
)),
),
);
}
}
@@ -36,7 +36,27 @@ class AdvancedFilterController extends BaseController {
_searchController.cleanSearchFilter();
}
void _updateFilterEmailFromAdvancedSearchView() {
if (fromFilterInputController.text.isNotEmpty) {
_searchEmailFilter.from.add(fromFilterInputController.text);
}
if (toFilterInputController.text.isNotEmpty) {
_searchEmailFilter.to.add(toFilterInputController.text);
}
_searchController.searchEmailFilter.value = _searchEmailFilter.copyWith(
subject: StringConvert.writeEmptyToNull(subjectFilterInputController.text),
hasKeyword: Wrapped.value(
StringConvert.writeEmptyToNull(hasKeyWordFilterInputController.text)),
notKeyword: Wrapped.value(
StringConvert.writeNullToEmpty(notKeyWordFilterInputController.text)),
mailbox: mailboxFilterSelectedFormAdvancedSearch.value,
emailReceiveTimeType: dateFilterSelectedFormAdvancedSearch.value,
hasAttachment: hasAttachment.value,
);
}
applyAdvancedSearchFilter(BuildContext context){
_updateFilterEmailFromAdvancedSearchView();
_mailboxDashBoardController.searchEmail(context, StringConvert.writeNullToEmpty(_searchEmailFilter.text?.value));
}
@@ -19,6 +19,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re
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/quick_search_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_bottom_sheet.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/search_state.dart';
@@ -29,6 +30,8 @@ class SearchController extends BaseController {
final SaveRecentSearchInteractor _saveRecentSearchInteractor;
final GetAllRecentSearchLatestInteractor _getAllRecentSearchLatestInteractor;
final ResponsiveUtils _responsiveUtils = Get.find<ResponsiveUtils>();
final searchInputController = TextEditingController();
final searchEmailFilter = SearchEmailFilter().obs;
final searchState = SearchState.initial().obs;
@@ -216,6 +219,14 @@ class SearchController extends BaseController {
emailReceiveTimeType.value = receiveTimeType;
}
showAdvancedFilterView(BuildContext context) async {
selectOpenAdvanceSearch();
if (_responsiveUtils.isMobile(context)) {
await showAdvancedSearchFilterBottomSheet(context);
selectOpenAdvanceSearch();
}
}
@override
void onDone() {}
@@ -52,6 +52,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/mark_as
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_email_drafts_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart';
@@ -89,6 +90,7 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.find<DeleteEmailPermanentlyInteractor>(),
Get.find<MarkAsMailboxReadInteractor>(),
));
Get.put(AdvancedFilterController());
}
@override
@@ -12,6 +12,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/base_mailb
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/composer_overlay_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/email_quick_search_item_tile_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recent_search_item_tile_widget.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/model/app_setting.dart';
@@ -235,7 +236,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
..text(AppLocalizations.of(context).mark_all_as_read, isVertical: false))
.build(),
const SizedBox(width: 16),
Obx(() => (ButtonBuilder(imagePaths.icFilterWeb)
Obx(() => (ButtonBuilder(imagePaths.icFilterAdvanced)
..key(const Key('button_filter_messages'))
..context(context)
..decoration(BoxDecoration(borderRadius: BorderRadius.circular(10), color: AppColor.colorButtonHeaderThread))
@@ -461,7 +462,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
fit: BoxFit.fill),
onTap: () {
searchController.clearTextSearch();
})
}),
rightButton: IconOpenAdvancedSearchWidget(context)
),
suggestionsBoxDecoration: QuickSearchSuggestionsBoxDecoration(
color: Colors.white,
@@ -0,0 +1,31 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
Future<void> showAdvancedSearchFilterBottomSheet(BuildContext context) async {
final ImagePaths _imagePaths = Get.find<ImagePaths>();
await FullScreenActionSheetBuilder(
context: context,
child: AdvancedSearchInputForm(),
cancelWidget: Padding(
padding: const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
_imagePaths.icCloseAdvancedSearch,
color: AppColor.colorHintSearchBar,
width: 24,
height: 24,
),
),
titleWidget: Text(
AppLocalizations.of(context).advancedSearch,
style: const TextStyle(
fontSize: 20,
color: AppColor.colorNameEmail,
),
),
).show();
}
@@ -0,0 +1,296 @@
import 'package:core/core.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.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/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/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
with PopupContextMenuActionMixin {
AdvancedSearchInputForm({
Key? key,
}) : super(key: key);
final ImagePaths imagePaths = Get.find<ImagePaths>();
final ResponsiveUtils responsiveUtils = Get.find<ResponsiveUtils>();
@override
Widget build(BuildContext context) {
controller.initSearchFilterField(context);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 12),
child: Material(
color: Colors.white,
child: Column(
children: [
_buildFilterField(
textEditingController: controller.fromFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.form,
),
_buildFilterField(
textEditingController: controller.toFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.to,
),
_buildFilterField(
textEditingController: controller.subjectFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.subject,
),
_buildFilterField(
textEditingController: controller.hasKeyWordFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.hasKeyword,
),
_buildFilterField(
textEditingController: controller.notKeyWordFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.notKeyword,
),
_buildFilterField(
textEditingController: controller.mailBoxFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.mailBox,
isSelectFormList: true,
),
_buildFilterField(
textEditingController: controller.dateFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.date,
isSelectFormList: true,
onTap: () {
openContextMenuAction(
context,
_buildEmailReceiveTimeTypeActionTiles(context),
);
},
) ,
_buildCheckboxHasAttachment(context),
Row(
children: [
Expanded(
child: _buildButton(
onAction: () {
controller.cleanSearchFilter();
},
colorButton: AppColor.primaryColor.withOpacity(0.14),
colorText: AppColor.primaryColor,
text: AppLocalizations.of(context).clearFilter,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildButton(
onAction: () {
controller.applyAdvancedSearchFilter(context);
popBack();
},
colorButton: AppColor.primaryColor,
colorText: AppColor.primaryLightColor,
text: AppLocalizations.of(context).search,
),
),
],
),
],
),
),
),
);
}
List<Widget> _buildEmailReceiveTimeTypeActionTiles(BuildContext context) {
return EmailReceiveTimeType.values
.map(
(e) => Material(
child: PopupMenuItem(
child: Text(e.getTitle(context)),
onTap: () {
controller.dateFilterSelectedFormAdvancedSearch.value = e;
controller.dateFilterInputController.text = e.getTitle(context);
},
),
),
)
.toList();
}
Widget _buildFilterField({
required BuildContext context,
required AdvancedSearchFilterField advancedSearchFilterField,
required TextEditingController textEditingController,
VoidCallback? onTap,
bool isSelectFormList = false,
}) {
final child = [
SizedBox(
width: responsiveUtils.isMobile(context) ? null : 112,
child: Text(
advancedSearchFilterField.getTitle(context),
style: const TextStyle(
fontSize: 14,
color: AppColor.colorContentEmail,
),
),
),
const Padding(padding: EdgeInsets.all(4)),
responsiveUtils.isMobile(context)
? _buildTextField(
isSelectFormList: isSelectFormList,
onTap: onTap,
context: context,
advancedSearchFilterField: advancedSearchFilterField,
textEditingController: textEditingController,
) : Expanded(
child: _buildTextField(
isSelectFormList: isSelectFormList,
onTap: onTap,
context: context,
advancedSearchFilterField: advancedSearchFilterField,
textEditingController: textEditingController,
),
)
];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: responsiveUtils.isMobile(context)
? Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: child,
)
: Row(children: child),
);
}
Widget _buildTextField({
required BuildContext context,
required AdvancedSearchFilterField advancedSearchFilterField,
required TextEditingController textEditingController,
VoidCallback? onTap,
bool isSelectFormList = false,
}) =>
TextField(
controller: textEditingController,
readOnly: isSelectFormList,
onTap: onTap,
decoration: InputDecoration(
filled: true,
fillColor: AppColor.loginTextFieldBackgroundColor,
contentPadding: const EdgeInsets.only(
right: 8,
left: 12,
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
hintText: advancedSearchFilterField.getHintText(context),
hintStyle: const TextStyle(
fontSize: 14,
color: AppColor.colorHintSearchBar,
),
suffixIconConstraints:
const BoxConstraints(minHeight: 24, minWidth: 24),
suffixIcon: isSelectFormList
? buildIconWeb(
icon: SvgPicture.asset(
imagePaths.icDropDown,
),
)
: null),
);
Widget _buildCheckboxHasAttachment(BuildContext context) {
return Obx(
()=> CheckboxListTile(
contentPadding: EdgeInsets.zero,
controlAffinity: ListTileControlAffinity.leading,
value: controller.hasAttachment.value,
onChanged: (value) {
controller.hasAttachment.value = value ?? false;
},
title: Text(AppLocalizations.of(context).hasAttachment),
),
);
}
Widget _buildButton({
required Color colorButton,
required Color colorText,
required String text,
required VoidCallback onAction,
}) {
return InkWell(
onTap: onAction,
onTapDown: (_) {
onAction.call();
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
alignment: Alignment.center,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: colorButton),
child: Text(
text,
style: TextStyle(fontSize: 17, color: colorText),
),
),
);
}
Widget _buildEmailReceiveTimeTypeDropDownButton(){
return DropdownButtonHideUnderline(
child: DropdownButton2<EmailReceiveTimeType>(
isExpanded: true,
hint: Row(
children: [
Expanded(child: Text(controller.dateFilterSelectedFormAdvancedSearch.value.name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
)),
],
),
items: EmailReceiveTimeType.values.map((item) => DropdownMenuItem<EmailReceiveTimeType>(
value: item,
child: Text(
item.name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
),
)).toList(),
value: controller.dateFilterSelectedFormAdvancedSearch.value,
onChanged: (item) {
controller.dateFilterSelectedFormAdvancedSearch.value = item!;
},
icon: SvgPicture.asset(imagePaths.icDropDown),
buttonPadding: const EdgeInsets.symmetric(horizontal: 12),
buttonDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.colorInputBorderCreateMailbox, width: 0.5),
color: AppColor.colorInputBackgroundCreateMailbox),
itemHeight: 44,
buttonHeight: 44,
selectedItemHighlightColor: Colors.black12,
itemPadding: const EdgeInsets.symmetric(horizontal: 12),
dropdownMaxHeight: 200,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.white),
dropdownElevation: 4,
scrollbarRadius: const Radius.circular(40),
scrollbarThickness: 6,
),
);
}
}
@@ -0,0 +1,36 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
class IconOpenAdvancedSearchWidget extends StatelessWidget {
IconOpenAdvancedSearchWidget(
this._parentContext, {
Key? key,
}) : super(key: key);
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
final controller = Get.find<SearchController>();
final BuildContext _parentContext;
@override
Widget build(BuildContext context) {
return Obx(
() => buildIconWeb(
minSize: 0,
iconPadding: const EdgeInsets.only(right: 8),
icon: SvgPicture.asset(_imagePaths.icFilterAdvanced,
color: controller.isAdvancedSearchViewOpen.isTrue
? AppColor.colorFilterMessageEnabled
: AppColor.colorFilterMessageDisabled,
width: 16,
height: 16),
onTap: _responsiveUtils.isMobile(context) ? () {controller.showAdvancedFilterView(_parentContext);} : null),
);
}
}
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_sea
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.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/quick_search_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/email_quick_search_item_tile_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recent_search_item_tile_widget.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
@@ -226,7 +227,8 @@ class ThreadView extends GetWidget<ThreadController> with AppLoaderMixin,
fit: BoxFit.fill),
onTap: () {
controller.clearTextSearch();
})
}),
rightButton: IconOpenAdvancedSearchWidget(context)
),
suggestionsBoxDecoration: const QuickSearchSuggestionsBoxDecoration(
color: Colors.white,
+7 -7
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2022-06-24T14:16:53.170960",
"@@last_modified": "2022-06-25T22:39:15.113430",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -1324,12 +1324,6 @@
"placeholders_order": [],
"placeholders": {}
},
"anyTime": "Any time",
"@anyTime": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"last30Days": "Last 30 days",
"@last30Days": {
"type": "text",
@@ -1495,5 +1489,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"advancedSearch": "Advanced search",
"@advancedSearch": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -1357,12 +1357,6 @@ class AppLocalizations {
name: 'showingResultsFor');
}
String get anyTime {
return Intl.message(
'Any time',
name: 'anyTime');
}
String get last30Days {
return Intl.message(
'Last 30 days',
@@ -1537,4 +1531,11 @@ class AppLocalizations {
name: 'clearFilter',
);
}
String get advancedSearch {
return Intl.message(
'Advanced search',
name: 'advancedSearch',
);
}
}