From e23f4b77560b0cdaaef36eb8a2dba8f4c5606ffc Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 15 Sep 2022 19:15:29 +0700 Subject: [PATCH] TF-933 Apply new design for `SearchInputForm` on Web Desktop --- .../quick_search/quick_search_input_form.dart | 155 ++++++---- .../views/search/search_bar_view.dart | 92 +++--- .../presentation/destination_picker_view.dart | 8 +- .../mailbox/presentation/mailbox_view.dart | 8 +- .../presentation/mailbox_view_web.dart | 8 +- .../mailbox_dashboard_view_web.dart | 289 ++++-------------- .../advanced_search_filter_overlay.dart | 11 +- .../widgets/search_input_form_widget.dart | 241 +++++++++++++++ .../thread/presentation/thread_view.dart | 11 +- 9 files changed, 456 insertions(+), 367 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart diff --git a/core/lib/presentation/views/quick_search/quick_search_input_form.dart b/core/lib/presentation/views/quick_search/quick_search_input_form.dart index 528023022..5e7432ea9 100644 --- a/core/lib/presentation/views/quick_search/quick_search_input_form.dart +++ b/core/lib/presentation/views/quick_search/quick_search_input_form.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:math'; import 'dart:io'; +import 'package:core/presentation/extensions/color_extension.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -85,6 +86,8 @@ class QuickSearchInputForm extends FormField { RecentSelectionCallback? onRecentSelected, EdgeInsets? listActionPadding, bool hideSuggestionsBox: false, + BoxDecoration? decoration, + double? maxHeight, }) : assert( initialValue == null || textFieldConfiguration.controller == null), assert(minCharsForSuggestions >= 0), @@ -146,6 +149,8 @@ class QuickSearchInputForm extends FormField { onRecentSelected: onRecentSelected, listActionPadding: listActionPadding, hideSuggestionsBox: hideSuggestionsBox, + decoration: decoration, + maxHeight: maxHeight, ); }); @@ -505,6 +510,10 @@ class TypeAheadFieldQuickSearch extends StatefulWidget { /// Padding button action final EdgeInsets? listActionPadding; final bool hideSuggestionsBox; + /// Box decoration search input + final BoxDecoration? decoration; + /// Max height search input + final double? maxHeight; /// Creates a [TypeAheadFieldQuickSearch] TypeAheadFieldQuickSearch( @@ -545,6 +554,8 @@ class TypeAheadFieldQuickSearch extends StatefulWidget { this.onRecentSelected, this.listActionPadding, this.hideSuggestionsBox = false, + this.decoration, + this.maxHeight, }) : assert(animationStart >= 0.0 && animationStart <= 1.0), assert( direction == AxisDirection.down || direction == AxisDirection.up), @@ -631,6 +642,7 @@ class _TypeAheadFieldQuickSearchState extends State extends State extends State extends State extends State<_SuggestionsList> final animationChild = widget.transitionBuilder != null ? widget.transitionBuilder!(context, child, this._animationController) : SizeTransition( - axisAlignment: -1.0, - sizeFactor: CurvedAnimation( - parent: this._animationController!, - curve: Curves.fastOutSlowIn), - child: child, - ); + axisAlignment: -1.0, + sizeFactor: CurvedAnimation( + parent: this._animationController!, + curve: Curves.fastOutSlowIn), + child: child, + ); BoxConstraints constraints; if (widget.decoration!.constraints == null) { @@ -1088,10 +1119,16 @@ class _SuggestionsListState extends State<_SuggestionsList> } var container = Material( - elevation: widget.decoration!.elevation, + elevation: widget.suggestionsBox?.isOpened == true + ? 1 + : widget.decoration!.elevation, color: widget.decoration!.color, shape: widget.decoration!.shape, - borderRadius: widget.decoration!.borderRadius, + borderRadius: widget.suggestionsBox?.isOpened == true + ? const BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16)) + : widget.decoration!.borderRadius, shadowColor: widget.decoration!.shadowColor, clipBehavior: widget.decoration!.clipBehavior, child: ConstrainedBox( @@ -1258,7 +1295,7 @@ class _SuggestionsListState extends State<_SuggestionsList> if (widget.decoration!.hasScrollbar) { child = Scrollbar( controller: _scrollController, - child: child, + child: Container(child: child), ); } diff --git a/core/lib/presentation/views/search/search_bar_view.dart b/core/lib/presentation/views/search/search_bar_view.dart index 2dd9344c8..54e5e7b24 100644 --- a/core/lib/presentation/views/search/search_bar_view.dart +++ b/core/lib/presentation/views/search/search_bar_view.dart @@ -6,62 +6,42 @@ import 'package:flutter_svg/flutter_svg.dart'; typedef OnOpenSearchViewAction = Function(); -class SearchBarView { - OnOpenSearchViewAction? _onOpenSearchViewAction; +class SearchBarView extends StatelessWidget { + final OnOpenSearchViewAction? onOpenSearchViewAction; final ImagePaths _imagePaths; + final double? heightSearchBar; + final EdgeInsets? padding; + final EdgeInsets? margin; + final String? hintTextSearch; + final double? maxSizeWidth; + final Widget? rightButton; + final double? radius; - double? _heightSearchBar; - EdgeInsets? _padding; - EdgeInsets? _margin; - String? _hintTextSearch; - double? _maxSizeWidth; - Widget? _rightButton; + const SearchBarView(this._imagePaths, { + this.heightSearchBar, + this.padding, + this.margin, + this.hintTextSearch, + this.maxSizeWidth, + this.rightButton, + this.onOpenSearchViewAction, + this.radius, + }); - - SearchBarView(this._imagePaths); - - void addOnOpenSearchViewAction(OnOpenSearchViewAction onOpenSearchViewAction) { - _onOpenSearchViewAction = onOpenSearchViewAction; - } - - void setHeightSearchBar(double heightSearchBar) { - _heightSearchBar = heightSearchBar; - } - - void addPadding(EdgeInsets padding) { - _padding = padding; - } - - void addRightButton(Widget rightButton) { - _rightButton = rightButton; - } - - void addMargin(EdgeInsets margin) { - _margin = margin; - } - - void hintTextSearch(String text) { - _hintTextSearch = text; - } - - void maxSizeWidth(double? size) { - _maxSizeWidth = size; - } - - Widget build() { + Widget build(BuildContext context) { return Container( key: Key('search_bar_widget'), alignment: Alignment.center, - height: _heightSearchBar ?? 40, - width: _maxSizeWidth ?? double.infinity, + height: heightSearchBar ?? 40, + width: maxSizeWidth ?? double.infinity, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(radius ?? 10), color: AppColor.colorBgSearchBar), - padding: _padding ?? EdgeInsets.zero, - margin: _margin ?? EdgeInsets.zero, + padding: padding ?? EdgeInsets.zero, + margin: margin ?? EdgeInsets.zero, child: InkWell( - onTap: () => _onOpenSearchViewAction?.call(), + onTap: onOpenSearchViewAction, mouseCursor: SystemMouseCursors.text, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -71,16 +51,24 @@ class SearchBarView { splashRadius: 15, minSize: 40, iconPadding: EdgeInsets.zero, - icon: SvgPicture.asset(_imagePaths.icSearchBar, width: 16, height: 16, fit: BoxFit.fill), - onTap: () => _onOpenSearchViewAction?.call()), + icon: SvgPicture.asset( + _imagePaths.icSearchBar, + width: 16, + height: 16, + fit: BoxFit.fill), + onTap: onOpenSearchViewAction), Expanded( child: Text( - _hintTextSearch ?? '', + hintTextSearch ?? '', maxLines: 1, - style: TextStyle(fontSize: kIsWeb ? 15 : 17, color: AppColor.colorHintSearchBar)), + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: TextStyle( + fontSize: kIsWeb ? 15 : 17, + color: AppColor.colorHintSearchBar)), ), - if(_rightButton != null) - _rightButton! + if(rightButton != null) + rightButton! ] ), ), diff --git a/lib/features/destination_picker/presentation/destination_picker_view.dart b/lib/features/destination_picker/presentation/destination_picker_view.dart index b1d9597e9..72f785281 100644 --- a/lib/features/destination_picker/presentation/destination_picker_view.dart +++ b/lib/features/destination_picker/presentation/destination_picker_view.dart @@ -232,10 +232,10 @@ class DestinationPickerView extends GetWidget left: _responsiveUtils.isLandscapeMobile(context) && !BuildUtils.isWeb ? 0 : 16, right: 16), - child: (SearchBarView(_imagePaths) - ..hintTextSearch(AppLocalizations.of(context).hint_search_mailboxes) - ..addOnOpenSearchViewAction(() => controller.enableSearch())) - .build()); + child: SearchBarView( + _imagePaths, + hintTextSearch: AppLocalizations.of(context).hint_search_mailboxes, + onOpenSearchViewAction: controller.enableSearch)); } Widget _buildBodyDestinationPicker(BuildContext context, MailboxActions? actions) { diff --git a/lib/features/mailbox/presentation/mailbox_view.dart b/lib/features/mailbox/presentation/mailbox_view.dart index dadf88e6b..3f7647576 100644 --- a/lib/features/mailbox/presentation/mailbox_view.dart +++ b/lib/features/mailbox/presentation/mailbox_view.dart @@ -187,10 +187,10 @@ class MailboxView extends GetWidget { bottom: 16, left: _responsiveUtils.isLandscapeMobile(context) ? 0 : 16, right: 16), - child: (SearchBarView(_imagePaths) - ..hintTextSearch(AppLocalizations.of(context).hint_search_mailboxes) - ..addOnOpenSearchViewAction(() => controller.enableSearch())) - .build()); + child: SearchBarView( + _imagePaths, + hintTextSearch: AppLocalizations.of(context).hint_search_mailboxes, + onOpenSearchViewAction: controller.enableSearch)); } Widget _buildLoadingView() { diff --git a/lib/features/mailbox/presentation/mailbox_view_web.dart b/lib/features/mailbox/presentation/mailbox_view_web.dart index 785b0952a..90fb3d141 100644 --- a/lib/features/mailbox/presentation/mailbox_view_web.dart +++ b/lib/features/mailbox/presentation/mailbox_view_web.dart @@ -130,10 +130,10 @@ class MailboxView extends GetWidget with AppLoaderMixin, Popu Widget _buildSearchBarWidget(BuildContext context) { return Padding( padding: EdgeInsets.only(top: 16, bottom: 16, right: 4, left: _responsiveUtils.isDesktop(context) ? 0 : 12), - child: (SearchBarView(_imagePaths) - ..hintTextSearch(AppLocalizations.of(context).hint_search_mailboxes) - ..addOnOpenSearchViewAction(() => controller.enableSearch())) - .build()); + child: SearchBarView( + _imagePaths, + hintTextSearch: AppLocalizations.of(context).hint_search_mailboxes, + onOpenSearchViewAction: controller.enableSearch)); } Widget _buildLoadingView() { 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 1e27e463e..81d21bd8c 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -8,7 +8,6 @@ import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.d import 'package:tmail_ui_user/features/email/presentation/email_view.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view_web.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/base_mailbox_dashboard_view.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; @@ -18,8 +17,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_overlay.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/download/download_task_item_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/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; @@ -63,7 +61,9 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { Container( width: responsiveUtils.defaultSizeMenu, color: Colors.white, - padding: const EdgeInsets.only(top: 25, bottom: 25, left: 28), + padding: const EdgeInsets.only(left: 28), + alignment: Alignment.center, + height: 80, child: Row(children: [ (SloganBuilder(arrangedByHorizontal: true) ..setSloganText(AppLocalizations.of(context).app_name) @@ -88,7 +88,9 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ), Expanded(child: Container( color: Colors.white, - padding: const EdgeInsets.only(right: 10, top: 16, bottom: 10), + alignment: Alignment.center, + padding: const EdgeInsets.only(right: 10), + height: 80, child: _buildRightHeader(context))) ]), Expanded(child: Row( @@ -176,44 +178,49 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } Widget _buildRightHeader(BuildContext context) { - return Row(children: [ - Obx(() => !searchController.isSearchActive() ? const Spacer() : const SizedBox.shrink()), - Obx(() { - if (searchController.isSearchActive()) { - return Expanded(child: _buildSearchForm(context)); - } else { - return PortalTarget( - visible: searchController.isAdvancedSearchViewOpen.isTrue, - portalFollower: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => searchController.selectOpenAdvanceSearch()), - child: PortalTarget( - visible: searchController.isAdvancedSearchViewOpen.isTrue, - anchor: const Aligned( - follower: Alignment.topRight, - target: Alignment.bottomRight, - widthFactor: 3, - backup: Aligned( - follower: Alignment.topRight, - target: Alignment.bottomRight, - widthFactor: 3, + return LayoutBuilder(builder: (context, constraint) { + return Row(children: [ + Container( + width: constraint.maxWidth / 2, + height: 52, + color: Colors.transparent, + child: Obx(() { + if (searchController.isSearchActive()) { + return SearchInputFormWidget( + maxWidth: constraint.maxWidth / 2, + dashBoardController: controller, + imagePaths: imagePaths); + } else { + return PortalTarget( + visible: searchController.isAdvancedSearchViewOpen.isTrue, + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => searchController.selectOpenAdvanceSearch()), + child: PortalTarget( + visible: searchController.isAdvancedSearchViewOpen.isTrue, + anchor: const Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + backup: Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + ), + ), + portalFollower: AdvancedSearchFilterOverlay(maxWidth: constraint.maxWidth / 2), + child: SearchBarView(imagePaths, + hintTextSearch: AppLocalizations.of(context).search_emails, + onOpenSearchViewAction: controller.searchController.enableSearch, + heightSearchBar: 52, + radius: 12, + rightButton: IconOpenAdvancedSearchWidget(context)), ), - ), - portalFollower: responsiveUtils.isMobile(context) - ? const SizedBox.shrink() - : const AdvancedSearchFilterOverlay(), - child: (SearchBarView(imagePaths) - ..hintTextSearch(AppLocalizations.of(context).search_emails) - ..maxSizeWidth(240) - ..addOnOpenSearchViewAction(() => searchController.enableSearch()) - ..addRightButton(IconOpenAdvancedSearchWidget(context))) - .build(), - ), - ); - } - }), - Obx(() => !searchController.isSearchActive() ? const SizedBox(width: 16) : const SizedBox.shrink()), - Obx(() => (AvatarBuilder() + ); + } + })), + const Spacer(), + Obx(() => (AvatarBuilder() ..text(controller.userProfile.value?.getAvatarText() ?? '') ..backgroundColor(Colors.white) ..textColor(Colors.black) @@ -233,9 +240,10 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { color: AppColor.colorShadowBgContentEmail, spreadRadius: 1, blurRadius: 1, offset: Offset(0, 0.5))]) ..size(48)) - .build()), - const SizedBox(width: 16) - ]); + .build()), + const SizedBox(width: 16) + ]); + }); } Widget _buildListButtonTopBar(BuildContext context) { @@ -420,197 +428,6 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { controller.selectedMailbox.value?.isDrafts == true; } - Widget _buildSearchForm(BuildContext context) { - return Row( - children: [ - buildIconWeb( - icon: SvgPicture.asset( - imagePaths.icBack, - color: AppColor.colorTextButton, - fit: BoxFit.fill), - onTap: () => controller.dispatchAction(DisableSearchEmailAction())), - Expanded(child: Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: AppColor.colorBgSearchBar), - height: 45, - child: PortalTarget( - visible: searchController.isAdvancedSearchViewOpen.isTrue, - portalFollower: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => searchController.selectOpenAdvanceSearch()), - child: PortalTarget( - visible: searchController.isAdvancedSearchViewOpen.isTrue, - anchor: const Aligned( - follower: Alignment.topLeft, - target: Alignment.bottomLeft, - widthFactor: 1, - backup: Aligned( - follower: Alignment.topLeft, - target: Alignment.bottomLeft, - widthFactor: 1, - ), - ), - portalFollower: responsiveUtils.isMobile(context) - ? const SizedBox.shrink() - : const AdvancedSearchFilterOverlay(), - child: QuickSearchInputForm( - textFieldConfiguration: QuickSearchTextFieldConfiguration( - controller: searchController.searchInputController, - autofocus: true, - enabled: searchController.isAdvancedSearchViewOpen.isFalse, - focusNode: searchController.searchFocus, - textInputAction: TextInputAction.done, - onSubmitted: (keyword) { - if (keyword.trim().isNotEmpty) { - searchController.saveRecentSearch(RecentSearch.now(keyword)); - } - controller.searchEmail(context, keyword); - }, - onChanged: (query) { - log('MailboxDashBoardView::_buildSearchForm(): onChanged: $query'); - searchController.onChangeTextSearch(query); - }, - decoration: InputDecoration( - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - contentPadding: EdgeInsets.zero, - hintText: AppLocalizations.of(context).search_mail, - hintStyle: const TextStyle( - color: AppColor.colorHintSearchBar, - fontSize: 17.0), - labelStyle: const TextStyle( - color: AppColor.colorHintSearchBar, - fontSize: 17.0) - ), - leftButton: buildIconWeb( - icon: SvgPicture.asset( - imagePaths.icSearchBar, - width: 16, - height: 16, - fit: BoxFit.fill), - onTap: () { - final keyword = searchController.searchInputController.text; - if (keyword.trim().isNotEmpty) { - searchController.saveRecentSearch(RecentSearch.now(keyword)); - } - controller.searchEmail(context, keyword); - }), - clearTextButton: buildIconWeb( - icon: SvgPicture.asset( - imagePaths.icClearTextSearch, - width: 16, - height: 16, - fit: BoxFit.fill), - onTap: () { - searchController.clearTextSearch(); - }), - rightButton: IconOpenAdvancedSearchWidget(context) - ), - suggestionsBoxDecoration: QuickSearchSuggestionsBoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(16)), - constraints: BoxConstraints( - maxWidth: responsiveUtils.isDesktop(context) - ? 556 - : double.infinity), - ), - debounceDuration: const Duration(milliseconds: 500), - listActionButton: const [ - QuickSearchFilter.hasAttachment, - QuickSearchFilter.last7Days, - QuickSearchFilter.fromMe, - ], - actionButtonBuilder: (context, filterAction) { - if (filterAction is QuickSearchFilter) { - return buildQuickSearchFilterButton(context, filterAction); - } - return const SizedBox.shrink(); - }, - buttonActionCallback: (filterAction) { - if (filterAction is QuickSearchFilter) { - controller.selectQuickSearchFilter( - quickSearchFilter: filterAction, - fromSuggestionBox: true, - ); - } - }, - listActionPadding: const EdgeInsets.only( - left: 24, right: 24, top: 20, bottom: 12), - titleHeaderRecent: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), - child: Text(AppLocalizations.of(context).recent, - style: const TextStyle(fontSize: 13.0, - color: AppColor.colorTextButtonHeaderThread, - fontWeight: FontWeight.w500) - ) - ), - buttonShowAllResult: (context, keyword) { - if (keyword is String) { - return InkWell( - onTap: () { - if (keyword.trim().isNotEmpty) { - searchController.saveRecentSearch(RecentSearch.now(keyword)); - } - controller.searchEmail(context, keyword); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 14), - child: Row(children: [ - Text(AppLocalizations.of(context).showingResultsFor, - style: const TextStyle(fontSize: 13.0, - color: AppColor.colorTextButtonHeaderThread, - fontWeight: FontWeight.w500) - ), - const SizedBox(width: 4), - Expanded(child: Text('"$keyword"', - style: const TextStyle( - fontSize: 13.0, - color: Colors.black, - fontWeight: FontWeight.w500))) - ]) - ), - ); - } else { - return const SizedBox.shrink(); - } - }, - loadingBuilder: (context) => Padding( - padding: const EdgeInsets.only(bottom: 16), - child: loadingWidget, - ), - fetchRecentActionCallback: (pattern) async { - return searchController.getAllRecentSearchAction(pattern); - }, - itemRecentBuilder: (context, recent) { - return RecentSearchItemTileWidget(recent); - }, - onRecentSelected: (recent) { - searchController.searchInputController.text = recent.value; - controller.searchEmail(context, recent.value); - }, - suggestionsCallback: (pattern) async { - return controller.quickSearchEmails(); - }, - itemBuilder: (context, email) { - return EmailQuickSearchItemTileWidget( - email, controller.selectedMailbox.value); - }, - onSuggestionSelected: (presentationEmail) async { - controller.dispatchAction( - OpenEmailDetailedAction(context, presentationEmail)); - }), - ), - ), - )), - const SizedBox(width: 16), - ] - ); - } - Widget _buildMarkAsMailboxReadLoading(BuildContext context) { return Obx(() { final viewState = controller.viewStateMarkAsReadMailbox.value; diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart index 87b0901c5..cbe13107f 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart @@ -7,7 +7,12 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/ad class AdvancedSearchFilterOverlay extends StatelessWidget { - const AdvancedSearchFilterOverlay({Key? key}) : super(key: key); + final double? maxWidth; + + const AdvancedSearchFilterOverlay({ + Key? key, + this.maxWidth + }) : super(key: key); @override Widget build(BuildContext context) { @@ -19,10 +24,10 @@ class AdvancedSearchFilterOverlay extends StatelessWidget { padding: const EdgeInsets.only(top: 8, bottom: 16), child: Container( constraints: BoxConstraints( - minWidth: 660, + minWidth: maxWidth ?? 660, maxHeight: _getHeightOverlay(context, responsiveUtils), ), - width: 660, + width: maxWidth ?? 660, height: _getHeightOverlay(context, responsiveUtils), padding: responsiveUtils.landscapeTabletSupported(context) ? EdgeInsets.zero 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 new file mode 100644 index 000000000..49c139ef3 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart @@ -0,0 +1,241 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.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/advanced_search_filter_overlay.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/main/localizations/app_localizations.dart'; + +class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { + + final MailboxDashBoardController dashBoardController; + final ImagePaths imagePaths; + final double maxWidth; + + const SearchInputFormWidget({ + Key? key, + required this.dashBoardController, + required this.imagePaths, + required this.maxWidth, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final controller = dashBoardController.searchController; + + return PortalTarget( + visible: controller.isAdvancedSearchViewOpen.isTrue, + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => controller.selectOpenAdvanceSearch()), + child: PortalTarget( + visible: controller.isAdvancedSearchViewOpen.isTrue, + anchor: const Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + backup: Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + ), + ), + portalFollower: AdvancedSearchFilterOverlay(maxWidth: maxWidth), + child: QuickSearchInputForm( + maxHeight: 52, + suggestionsBoxVerticalOffset: 0.0, + textFieldConfiguration: QuickSearchTextFieldConfiguration( + controller: controller.searchInputController, + autofocus: true, + enabled: controller.isAdvancedSearchViewOpen.isFalse, + focusNode: controller.searchFocus, + textInputAction: TextInputAction.done, + onSubmitted: (keyword) { + final query = keyword.trim(); + if (query.isNotEmpty) { + controller.saveRecentSearch(RecentSearch.now(query)); + dashBoardController.searchEmail(context, query); + } + }, + onChanged: controller.onChangeTextSearch, + decoration: InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + hintText: AppLocalizations.of(context).search_emails, + hintStyle: const TextStyle( + color: AppColor.colorHintSearchBar, + fontSize: 16.0), + labelStyle: const TextStyle( + color: Colors.black, + fontSize: 16.0) + ), + leftButton: buildIconWeb( + icon: SvgPicture.asset( + imagePaths.icSearchBar, + width: 16, + height: 16, + fit: BoxFit.fill), + onTap: () { + final keyword = controller.searchInputController.text.trim(); + if (keyword.isNotEmpty) { + controller.saveRecentSearch(RecentSearch.now(keyword)); + dashBoardController.searchEmail(context, keyword); + } + }), + clearTextButton: buildIconWeb( + icon: SvgPicture.asset( + imagePaths.icClearTextSearch, + width: 16, + height: 16, + fit: BoxFit.fill), + onTap: controller.clearTextSearch), + rightButton: IconOpenAdvancedSearchWidget(context) + ), + suggestionsBoxDecoration: QuickSearchSuggestionsBoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(16)), + constraints: BoxConstraints(maxWidth: maxWidth)), + debounceDuration: const Duration(milliseconds: 500), + listActionButton: QuickSearchFilter.values, + actionButtonBuilder: (context, filterAction) { + if (filterAction is QuickSearchFilter) { + return buildListButtonForQuickSearchForm(context, filterAction); + } else { + return const SizedBox.shrink(); + } + }, + buttonActionCallback: (filterAction) { + if (filterAction is QuickSearchFilter) { + dashBoardController.selectQuickSearchFilter( + quickSearchFilter: filterAction, + fromSuggestionBox: true, + ); + } + }, + listActionPadding: const EdgeInsets.only( + left: 12, + right: 12, + top: 12, + bottom: 6), + titleHeaderRecent: Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + bottom: 8, + top: 12), + child: Text(AppLocalizations.of(context).recent, + style: const TextStyle( + fontSize: 13.0, + color: AppColor.colorTextButtonHeaderThread, + fontWeight: FontWeight.w500) + ) + ), + buttonShowAllResult: (context, keyword) { + if (keyword is String) { + return InkWell( + onTap: () { + final query = keyword.trim(); + if (query.isNotEmpty) { + controller.saveRecentSearch(RecentSearch.now(query)); + dashBoardController.searchEmail(context, query); + } + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row(children: [ + Text(AppLocalizations.of(context).showingResultsFor, + style: const TextStyle( + fontSize: 13.0, + color: AppColor.colorTextButtonHeaderThread, + fontWeight: FontWeight.w500)), + const SizedBox(width: 4), + Expanded(child: Text('"$keyword"', + style: const TextStyle( + fontSize: 13.0, + color: Colors.black, + fontWeight: FontWeight.w500))) + ]) + ), + ); + } else { + return const SizedBox.shrink(); + } + }, + loadingBuilder: (context) => Padding( + padding: const EdgeInsets.only(bottom: 16), + child: loadingWidget), + fetchRecentActionCallback: controller.getAllRecentSearchAction, + itemRecentBuilder: (context, recent) => + RecentSearchItemTileWidget(recent), + onRecentSelected: (recent) { + controller.searchInputController.text = recent.value; + dashBoardController.searchEmail(context, recent.value); + }, + suggestionsCallback: (pattern) => + dashBoardController.quickSearchEmails(), + itemBuilder: (context, email) => + EmailQuickSearchItemTileWidget(email, dashBoardController.selectedMailbox.value), + onSuggestionSelected: (presentationEmail) => + dashBoardController.dispatchAction( + OpenEmailDetailedAction + (context, + presentationEmail))), + ), + ); + } + + Widget buildListButtonForQuickSearchForm(BuildContext context, QuickSearchFilter filter) { + final controller = dashBoardController.searchController; + + return Obx(() { + final isFilterSelected = dashBoardController.checkQuickSearchFilterSelected( + quickSearchFilter: filter); + + return Chip( + labelPadding: const EdgeInsets.only( + top: 2, + bottom: 2, + right: 10), + label: Text( + filter.getTitle( + context, + receiveTimeType: controller.emailReceiveTimeType.value), + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: filter.getTextStyle( + quickSearchFilterSelected: 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), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: filter.getBackgroundColor( + quickSearchFilterSelected: isFilterSelected)), + ), + ); + }); + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 250869437..184a573e4 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -176,11 +176,12 @@ class ThreadView extends GetWidget with AppLoaderMixin, portalFollower: _responsiveUtils.isMobile(context) ? const SizedBox.shrink() : const AdvancedSearchFilterOverlay(), - child: (SearchBarView(_imagePaths) - ..hintTextSearch(AppLocalizations.of(context).search_emails) - ..addOnOpenSearchViewAction(() => controller.enableSearch(context)) - ..addRightButton(IconOpenAdvancedSearchWidget(context))) - .build(), + child: SearchBarView( + _imagePaths, + hintTextSearch: AppLocalizations.of(context).search_emails, + onOpenSearchViewAction: () => controller.enableSearch(context), + rightButton: IconOpenAdvancedSearchWidget(context), + ), ), ); }