diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 161638c5d..cf89bc277 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -132,6 +132,8 @@ class MailboxController extends BaseController { } void _setUpMapMailboxIdDefault(List defaultMailboxList, List folderMailboxList) { + final allMailbox = defaultMailboxList + folderMailboxList; + final mapDefaultMailboxId = Map.fromIterable( defaultMailboxList, key: (presentationMailbox) => presentationMailbox.role!, @@ -142,13 +144,15 @@ class MailboxController extends BaseController { key: (presentationMailbox) => presentationMailbox.role!, value: (presentationMailbox) => presentationMailbox); - final mapFolderMailbox = Map.fromIterable( - folderMailboxList, + final mapMailbox = Map.fromIterable( + allMailbox, key: (presentationMailbox) => presentationMailbox.id, value: (presentationMailbox) => presentationMailbox); mailboxDashBoardController.setMapDefaultMailboxId(mapDefaultMailboxId); + mailboxDashBoardController.setMapMailbox(mapMailbox); + var mailboxCurrent = mailboxDashBoardController.selectedMailbox.value; if (mailboxCurrent != null) { @@ -157,15 +161,14 @@ class MailboxController extends BaseController { ? mapDefaultMailbox[mailboxCurrent.role] : mailboxCurrent); } else { - mailboxDashBoardController.setNewFirstSelectedMailbox(mapFolderMailbox.containsKey(mailboxCurrent.id) - ? mapFolderMailbox[mailboxCurrent.id] + mailboxDashBoardController.setNewFirstSelectedMailbox(mapMailbox.containsKey(mailboxCurrent.id) + ? mapMailbox[mailboxCurrent.id] : mailboxCurrent); } } else { if (mapDefaultMailbox.containsKey(PresentationMailbox.roleInbox)) { mailboxDashBoardController.setNewFirstSelectedMailbox(mapDefaultMailbox[PresentationMailbox.roleInbox]); } else { - final allMailbox = defaultMailboxList + folderMailboxList; if (allMailbox.isNotEmpty) { mailboxDashBoardController.setNewFirstSelectedMailbox(allMailbox.first); } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart index e863b7bb9..f4ff65adb 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart @@ -12,6 +12,10 @@ import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.da import 'package:tmail_ui_user/features/email/presentation/email_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_user_profile_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_user_profile_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.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'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class MailboxDashBoardController extends BaseController { @@ -24,9 +28,15 @@ class MailboxDashBoardController extends BaseController { final selectedEmail = Rxn(); final accountId = Rxn(); final userProfile = Rxn(); + final searchState = SearchState.initial().obs; + final suggestionSearch = [].obs; + TextEditingController searchInputController = TextEditingController(); + FocusNode searchFocus = FocusNode(); + SearchQuery? searchQuery; Session? sessionCurrent; Map mapDefaultMailboxId = Map(); + Map mapMailbox = Map(); MailboxDashBoardController(this._getUserProfileInteractor, this._appToast); @@ -95,6 +105,10 @@ class MailboxDashBoardController extends BaseController { mapDefaultMailboxId = newMapMailboxId; } + void setMapMailbox(Map newMapMailbox) { + mapMailbox = newMapMailbox; + } + void setSelectedMailbox(PresentationMailbox? newPresentationMailbox) { selectedMailbox.value = newPresentationMailbox; } @@ -120,9 +134,51 @@ class MailboxDashBoardController extends BaseController { scaffoldKey.currentState?.openEndDrawer(); } + bool isSearchActive() { + return searchState.value.searchStatus == SearchStatus.ACTIVE; + } + + void enableSearch() { + searchState.value = searchState.value.enableSearchState(); + } + + void disableSearch() { + searchState.value = searchState.value.disableSearchState(); + searchQuery = SearchQuery(''); + clearSuggestionSearch(); + searchInputController.clear(); + FocusManager.instance.primaryFocus?.unfocus(); + } + + void clearSearchText() { + searchQuery = SearchQuery(''); + clearSuggestionSearch(); + searchFocus.requestFocus(); + } + + void clearSuggestionSearch() { + suggestionSearch.clear(); + } + + void addSuggestionSearch(String query) { + if (query.trim().isNotEmpty) { + suggestionSearch.value = [query]; + } else { + clearSearchText(); + } + } + + void searchEmail(String value) { + searchQuery = SearchQuery(value); + dispatchState(Right(SearchEmailNewQuery(searchQuery ?? SearchQuery('')))); + clearSuggestionSearch(); + FocusManager.instance.primaryFocus?.unfocus(); + } + @override void onClose() { - super.onClose(); + searchInputController.dispose(); Get.delete(); + super.onClose(); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/model/search_state.dart b/lib/features/thread/presentation/model/search_state.dart new file mode 100644 index 000000000..e830f2b8f --- /dev/null +++ b/lib/features/thread/presentation/model/search_state.dart @@ -0,0 +1,24 @@ + +import 'package:equatable/equatable.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart'; + +class SearchState with EquatableMixin { + final SearchStatus searchStatus; + + SearchState(this.searchStatus); + + factory SearchState.initial() { + return SearchState(SearchStatus.INACTIVE); + } + + SearchState disableSearchState() { + return SearchState(SearchStatus.INACTIVE); + } + + SearchState enableSearchState() { + return SearchState(SearchStatus.ACTIVE); + } + + @override + List get props => [searchStatus]; +} \ No newline at end of file diff --git a/lib/features/thread/presentation/model/search_status.dart b/lib/features/thread/presentation/model/search_status.dart new file mode 100644 index 000000000..a4e054c0f --- /dev/null +++ b/lib/features/thread/presentation/model/search_status.dart @@ -0,0 +1,4 @@ + +enum SearchStatus { + ACTIVE, INACTIVE +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_bindings.dart b/lib/features/thread/presentation/thread_bindings.dart index bc0c0c2c1..ca8169f03 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -29,6 +29,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_e import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; class ThreadBindings extends Bindings { @@ -67,6 +68,7 @@ class ThreadBindings extends Bindings { Get.lazyPut(() => MarkAsStarMultipleEmailInteractor(Get.find())); Get.lazyPut(() => RefreshChangesEmailsInMailboxInteractor(Get.find())); Get.lazyPut(() => LoadMoreEmailsInMailboxInteractor(Get.find())); + Get.lazyPut(() => SearchEmailInteractor(Get.find())); Get.put(ThreadController( Get.find(), Get.find(), @@ -78,6 +80,7 @@ class ThreadBindings extends Bindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 6e672c0fe..5550e94a3 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -21,17 +21,20 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_ import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.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/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.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'; @@ -50,14 +53,18 @@ class ThreadController extends BaseController { final MarkAsStarMultipleEmailInteractor _markAsStarMultipleEmailInteractor; final RefreshChangesEmailsInMailboxInteractor _refreshChangesEmailsInMailboxInteractor; final LoadMoreEmailsInMailboxInteractor _loadMoreEmailsInMailboxInteractor; + final SearchEmailInteractor _searchEmailInteractor; final emailList = [].obs; + final emailListSearch = [].obs; final currentSelectMode = SelectMode.INACTIVE.obs; bool canLoadMore = true; MailboxId? _currentMailboxId; jmap.State? _currentEmailState; + SearchQuery? get searchQuery => mailboxDashBoardController.searchQuery; + EmailFilterCondition? get _filterCondition => EmailFilterCondition( inMailbox: mailboxDashBoardController.selectedMailbox.value?.id); @@ -78,6 +85,7 @@ class ThreadController extends BaseController { this._markAsStarMultipleEmailInteractor, this._refreshChangesEmailsInMailboxInteractor, this._loadMoreEmailsInMailboxInteractor, + this._searchEmailInteractor, ); @override @@ -107,6 +115,8 @@ class ThreadController extends BaseController { cancelSelectEmail(); _refreshEmailChanges(); mailboxDashBoardController.clearState(); + } else if (success is SearchEmailNewQuery){ + _searchEmail(); } }); }); @@ -127,6 +137,8 @@ class ThreadController extends BaseController { (failure) { if (failure is LoadMoreEmailsFailure) { canLoadMore = false; + } else if (failure is SearchEmailFailure) { + emailListSearch.clear(); } }, (success) { @@ -134,6 +146,8 @@ class ThreadController extends BaseController { _getAllEmailSuccess(success); } else if (success is LoadMoreEmailsSuccess) { _loadMoreEmailsSuccess(success); + } else if (success is SearchEmailSuccess) { + _searchEmailsSuccess(success); } } ); @@ -181,6 +195,8 @@ class ThreadController extends BaseController { dispatchState(Right(LoadingState())); emailList.value = []; canLoadMore = true; + disableSearch(); + cancelSelectEmail(); } void _getAllEmailSuccess(GetAllEmailSuccess success) { @@ -220,8 +236,10 @@ class ThreadController extends BaseController { } void loadMoreEmails() { - if (canLoadMore) { - if (_accountId != null) { + if (isSearchActive()) { + return; + } else { + if (canLoadMore && _accountId != null) { consumeState(_loadMoreEmailsInMailboxInteractor.execute( _accountId!, limit: ThreadConstants.defaultLimit, @@ -257,7 +275,16 @@ class ThreadController extends BaseController { } void selectEmail(BuildContext context, PresentationEmail presentationEmailSelected) { - emailList.value = emailList.map((email) => email.id == presentationEmailSelected.id ? email.toggleSelect() : email).toList(); + if (isSearchActive()) { + emailListSearch.value = emailListSearch + .map((email) => email.id == presentationEmailSelected.id ? email.toggleSelect() : email) + .toList(); + } else { + emailList.value = emailList + .map((email) => email.id == presentationEmailSelected.id ? email.toggleSelect() : email) + .toList(); + } + if (_isUnSelectedAll()) { currentSelectMode.value = SelectMode.INACTIVE; } else { @@ -267,16 +294,32 @@ class ThreadController extends BaseController { } } - List getListEmailSelected() => emailList.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); + List getListEmailSelected() { + if (isSearchActive()) { + return emailListSearch.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); + } else { + return emailList.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); + } + } - bool _isUnSelectedAll() => emailList.every((email) => email.selectMode == SelectMode.INACTIVE); + bool _isUnSelectedAll() { + if (isSearchActive()) { + return emailListSearch.every((email) => email.selectMode == SelectMode.INACTIVE); + } else { + return emailList.every((email) => email.selectMode == SelectMode.INACTIVE); + } + } bool isAllEmailRead(List listEmail) => listEmail.every((email) => email.isReadEmail()); bool isAllEmailMarkAsStar(List listEmail) => listEmail.every((email) => email.isFlaggedEmail()); void cancelSelectEmail() { - emailList.value = emailList.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); + if (isSearchActive()) { + emailListSearch.value = emailListSearch.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); + } else { + emailList.value = emailList.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); + } currentSelectMode.value = SelectMode.INACTIVE; } @@ -471,6 +514,39 @@ class ThreadController extends BaseController { } } + bool isSearchActive() => mailboxDashBoardController.isSearchActive(); + + void enableSearch(BuildContext context) { + mailboxDashBoardController.enableSearch(); + } + + void disableSearch() { + emailListSearch.clear(); + mailboxDashBoardController.disableSearch(); + } + + void _searchEmail() { + emailListSearch.clear(); + + if (_accountId != null && searchQuery != null) { + consumeState(_searchEmailInteractor.execute( + _accountId!, + limit: ThreadConstants.defaultLimit, + sort: _sortOrder, + filter: EmailFilterCondition(text: searchQuery!.value), + properties: ThreadConstants.propertiesDefault, + )); + } + } + + void _searchEmailsSuccess(SearchEmailSuccess success) { + final resultEmailSearchList = success.emailList + .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailbox)) + .toList(); + + emailListSearch.value = resultEmailSearchList; + } + bool canComposeEmail() => mailboxDashBoardController.sessionCurrent != null && mailboxDashBoardController.userProfile.value != null && mailboxDashBoardController.mapDefaultMailboxId.containsKey(PresentationMailbox.roleOutbox); diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index b7426ba8c..1e3812589 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -5,11 +5,14 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/app_bar_thread_select_mode_active_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/app_bar_thread_widget_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_context_menu_action_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/search_app_bar_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/suggestion_box_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class ThreadView extends GetWidget { @@ -19,72 +22,100 @@ class ThreadView extends GetWidget { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: AppColor.primaryLightColor, - body: SafeArea( - right: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), - left: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), - child: Container( - alignment: Alignment.center, - padding: EdgeInsets.zero, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildAppBarThread(context), - Expanded(child: Container( - alignment: Alignment.center, - padding: EdgeInsets.zero, - color: responsiveUtils.isMobile(context) ? AppColor.bgMailboxListMail : AppColor.primaryLightColor, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, + return GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.white, + body: SafeArea( + right: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), + left: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.zero, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildAppBarThread(context), + Expanded(child: Stack( children: [ - _buildLoadingView(), - Expanded(child: _buildListEmail(context)), - _buildLoadingViewLoadMore() - ] - ) - )), - ] + Container( + alignment: Alignment.center, + padding: EdgeInsets.zero, + color: responsiveUtils.isMobile(context) ? AppColor.bgMailboxListMail : AppColor.primaryLightColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStatusResultSearch(context), + _buildLoadingView(), + Expanded(child: _buildListEmail(context)), + _buildLoadingViewLoadMore() + ] + ) + ), + _buildSuggestionBox(context) + ], + )), + ] + ) ) - ) + ), + floatingActionButton: Obx(() => !controller.isSearchActive() + ? FloatingActionButton( + elevation: 4.0, + child: new Icon(Icons.add), + backgroundColor: AppColor.appColor, + onPressed: () => controller.composeEmailAction()) + : SizedBox.shrink()) ), - floatingActionButton: FloatingActionButton( - elevation: 4.0, - child: new Icon(Icons.add), - backgroundColor: AppColor.appColor, - onPressed: () => controller.composeEmailAction() - ) ); } Widget _buildAppBarThread(BuildContext context) { - return Obx(() => controller.currentSelectMode.value == SelectMode.ACTIVE - ? _buildAppBarSelectModeActive(context) - : _buildAppBarNormal(context)); + return Obx(() { + return Stack( + children: [ + _buildAppBarNormal(context), + if (controller.isSearchActive()) _buildSearchForm(context), + if (controller.currentSelectMode.value == SelectMode.ACTIVE) _buildAppBarSelectModeActive(context), + ], + ); + }); + } + + Widget _buildSearchForm(BuildContext context) { + return (SearchAppBarWidget( + context, + imagePaths, + controller.searchQuery, + controller.mailboxDashBoardController.suggestionSearch, + controller.mailboxDashBoardController.searchFocus, + controller.mailboxDashBoardController.searchInputController) + ..addDecoration(BoxDecoration(color: Colors.white)) + ..addOnCancelSearchPressed(() => controller.disableSearch()) + ..addOnClearTextSearchAction(() => controller.mailboxDashBoardController.clearSearchText()) + ..addOnSuggestionSearchQuery((query) => controller.mailboxDashBoardController.addSuggestionSearch(query)) + ..addOnSearchTextAction((query) => controller.mailboxDashBoardController.searchEmail(query))) + .build(); } Widget _buildAppBarNormal(BuildContext context) { - return Obx(() => Padding( - padding: EdgeInsets.only(left: 15, right: 12), - child: (AppBarThreadWidgetBuilder( + return (AppBarThreadWidgetBuilder( context, - // imagePaths, + imagePaths, responsiveUtils, controller.mailboxDashBoardController.selectedMailbox.value, controller.mailboxDashBoardController.userProfile.value) ..onOpenUserInformationAction(() => {}) - // ..onOpenSearchMailActionClick(() => {}) + ..onOpenSearchMailActionClick(() => controller.enableSearch(context)) ..onOpenListMailboxActionClick(() => controller.openMailboxLeftMenu())) - .build())); + .build(); } Widget _buildAppBarSelectModeActive(BuildContext context) { - return Obx(() => Padding( - padding: EdgeInsets.only(left: 12, right: 12), - child: (AppBarThreadSelectModeActiveBuilder( + return (AppBarThreadSelectModeActiveBuilder( context, imagePaths, controller.getListEmailSelected(), @@ -96,7 +127,7 @@ class ThreadView extends GetWidget { controller.openContextMenuSelectedEmail(context, _contextMenuEmailActionTile(context, listEmail))) ..addOnOpenPopupMenuActionClick((listEmail, position) => controller.openPopupMenuSelectedEmail(context, position, _popupMenuEmailActionTile(context, listEmail)))) - .build())); + .build(); } List _contextMenuEmailActionTile(BuildContext context, List listEmail) { @@ -183,7 +214,7 @@ class ThreadView extends GetWidget { Widget _buildLoadingView() { return Obx(() => controller.viewState.value.fold( (failure) => SizedBox.shrink(), - (success) => success is LoadingState + (success) => success is LoadingState || success is SearchingState ? Center(child: Padding( padding: EdgeInsets.only(top: 16, bottom: 16), child: SizedBox( @@ -214,19 +245,42 @@ class ThreadView extends GetWidget { alignment: Alignment.center, padding: EdgeInsets.zero, color: responsiveUtils.isMobile(context) ? AppColor.bgMailboxListMail : Colors.white, - child: Obx(() => controller.currentSelectMode.value == SelectMode.INACTIVE - ? controller.emailList.isNotEmpty - ? RefreshIndicator( - color: AppColor.primaryColor, - onRefresh: () async => controller.refreshAllEmail(), - child: _buildListEmailBody(context, controller.emailList)) - : RefreshIndicator( - color: AppColor.primaryColor, - onRefresh: () async => controller.refreshAllEmail(), - child: _buildEmptyEmail(context)) - : controller.emailList.isNotEmpty - ? _buildListEmailBody(context, controller.emailList) - : _buildEmptyEmail(context))); + child: Obx(() { + if (controller.isSearchActive()) { + if (controller.mailboxDashBoardController.suggestionSearch.isNotEmpty) { + return SizedBox.shrink(); + } else { + return _buildResultSearchEmails(context, controller.emailListSearch); + } + } else { + return _buildResultListEmail(context, controller.emailList); + } + }) + ); + } + + Widget _buildResultSearchEmails(BuildContext context, List listPresentationEmail) { + return listPresentationEmail.isNotEmpty + ? _buildListEmailBody(context, listPresentationEmail) + : _buildEmptyEmail(context); + } + + Widget _buildResultListEmail(BuildContext context, List listPresentationEmail) { + if (controller.currentSelectMode.value == SelectMode.INACTIVE) { + return listPresentationEmail.isNotEmpty + ? RefreshIndicator( + color: AppColor.primaryColor, + onRefresh: () async => controller.refreshAllEmail(), + child: _buildListEmailBody(context, listPresentationEmail)) + : RefreshIndicator( + color: AppColor.primaryColor, + onRefresh: () async => controller.refreshAllEmail(), + child: _buildEmptyEmail(context)); + } else { + return listPresentationEmail.isNotEmpty + ? _buildListEmailBody(context, listPresentationEmail) + : _buildEmptyEmail(context); + } } Widget _buildListEmailBody(BuildContext context, List listPresentationEmail) { @@ -251,7 +305,9 @@ class ThreadView extends GetWidget { listPresentationEmail[index], responsiveUtils, controller.mailboxDashBoardController.selectedMailbox.value?.role, - controller.currentSelectMode.value) + controller.currentSelectMode.value, + controller.mailboxDashBoardController.searchState.value.searchStatus, + controller.searchQuery) ..onOpenEmailAction((selectedEmail) => controller.previewEmail(context, selectedEmail)) ..onSelectEmailAction((selectedEmail) => controller.selectEmail(context, selectedEmail)) ..addOnMarkAsStarEmailActionClick((selectedEmail) => controller.markAsStarEmail(selectedEmail))) @@ -262,13 +318,47 @@ class ThreadView extends GetWidget { Widget _buildEmptyEmail(BuildContext context) { return Obx(() => controller.viewState.value.fold( (failure) => SizedBox.shrink(), - (success) => !(success is LoadingState) + (success) => !(success is LoadingState) && !(success is SearchingState) ? (BackgroundWidgetBuilder(context) ..key(Key('empty_email_background')) ..image(SvgPicture.asset(imagePaths.icEmptyImageDefault, width: 120, height: 120, fit: BoxFit.fill)) - ..text(AppLocalizations.of(context).no_emails)) + ..text(controller.isSearchActive() + ? AppLocalizations.of(context).no_emails_matching_your_search + : AppLocalizations.of(context).no_emails)) .build() : SizedBox.shrink()) ); } + + Widget _buildSuggestionBox(BuildContext context) { + return Obx(() => controller.mailboxDashBoardController.suggestionSearch.isNotEmpty + ? (SuggestionBoxWidget( + context, + imagePaths, + controller.mailboxDashBoardController.suggestionSearch) + ..addOnSelectedSuggestion((suggestion) => controller.mailboxDashBoardController.searchEmail(suggestion))) + .build() + : SizedBox.shrink() + ); + } + + Widget _buildStatusResultSearch(BuildContext context) { + return Obx(() { + if (controller.isSearchActive()) { + return controller.emailListSearch.length > 0 + ? Container( + width: double.infinity, + margin: EdgeInsets.zero, + color: AppColor.bgStatusResultSearch, + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 24), + child: Text( + AppLocalizations.of(context).results, + style: TextStyle(color: AppColor.baseTextColor, fontSize: 14, fontWeight: FontWeight.w500), + )) + : SizedBox.shrink(); + } else { + return SizedBox.shrink(); + } + }); + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/app_bar_thread_select_mode_active_builder.dart b/lib/features/thread/presentation/widgets/app_bar_thread_select_mode_active_builder.dart index 6ac2d08f2..8c3e5c4e6 100644 --- a/lib/features/thread/presentation/widgets/app_bar_thread_select_mode_active_builder.dart +++ b/lib/features/thread/presentation/widgets/app_bar_thread_select_mode_active_builder.dart @@ -49,8 +49,10 @@ class AppBarThreadSelectModeActiveBuilder { Widget build() { return Container( key: Key('app_bar_thread_select_mode_active'), - alignment: Alignment.center, - padding: EdgeInsets.symmetric(vertical: 8), + alignment: Alignment.topCenter, + color: Colors.white, + margin: EdgeInsets.zero, + padding: EdgeInsets.only(left: 10, right: 16, top: 10, bottom: 10), child: MediaQuery( data: MediaQueryData(padding: EdgeInsets.zero), child: Row( @@ -77,15 +79,18 @@ class AppBarThreadSelectModeActiveBuilder { } Widget _buildBackButton() { - return IconButton( - color: AppColor.baseTextColor, - icon: SvgPicture.asset(_imagePaths.icComposerClose, color: AppColor.baseTextColor, fit: BoxFit.fill), - onPressed: () => { - if (_onCloseActionClick != null) { - _onCloseActionClick!() + return Material( + borderRadius: BorderRadius.circular(12), + color: Colors.transparent, + child: IconButton( + color: AppColor.baseTextColor, + icon: SvgPicture.asset(_imagePaths.icComposerClose, color: AppColor.baseTextColor, fit: BoxFit.fill), + onPressed: () => { + if (_onCloseActionClick != null) { + _onCloseActionClick!() + } } - } - ); + )); } Widget _buildListOptionButton() { @@ -102,15 +107,18 @@ class AppBarThreadSelectModeActiveBuilder { // _onRemoveEmailActionClick!(_listEmail) // } // }), - IconButton( - key: Key('button_unread_email_selected'), - color: AppColor.baseTextColor, - icon: SvgPicture.asset(_imagePaths.icEyeDisable, color: AppColor.baseTextColor, fit: BoxFit.fill), - onPressed: () => { - if (_onMarkAsEmailReadActionClick != null) { - _onMarkAsEmailReadActionClick!(_listEmail) - } - }), + Material( + borderRadius: BorderRadius.circular(12), + color: Colors.transparent, + child: IconButton( + key: Key('button_unread_email_selected'), + color: AppColor.baseTextColor, + icon: SvgPicture.asset(_imagePaths.icEyeDisable, color: AppColor.baseTextColor, fit: BoxFit.fill), + onPressed: () => { + if (_onMarkAsEmailReadActionClick != null) { + _onMarkAsEmailReadActionClick!(_listEmail) + } + })), GestureDetector( key: Key('button_menu_select_email'), onTap: () => { diff --git a/lib/features/thread/presentation/widgets/app_bar_thread_widget_builder.dart b/lib/features/thread/presentation/widgets/app_bar_thread_widget_builder.dart index 19c0e2a29..a378b3e6a 100644 --- a/lib/features/thread/presentation/widgets/app_bar_thread_widget_builder.dart +++ b/lib/features/thread/presentation/widgets/app_bar_thread_widget_builder.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -// import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -13,19 +13,19 @@ typedef OnOpenListMailboxActionClick = void Function(); typedef OnOpenUserInformationActionClick = void Function(); class AppBarThreadWidgetBuilder { - // OnOpenSearchMailActionClick? _onOpenSearchMailActionClick; + OnOpenSearchMailActionClick? _onOpenSearchMailActionClick; OnOpenListMailboxActionClick? _onOpenListMailboxActionClick; OnOpenUserInformationActionClick? _onOpenUserInformationActionClick; final BuildContext _context; - // final ImagePaths _imagePaths; + final ImagePaths _imagePaths; final ResponsiveUtils _responsiveUtils; final PresentationMailbox? _presentationMailbox; final UserProfile? _userProfile; AppBarThreadWidgetBuilder( this._context, - // this._imagePaths, + this._imagePaths, this._responsiveUtils, this._presentationMailbox, this._userProfile, @@ -36,9 +36,9 @@ class AppBarThreadWidgetBuilder { _onOpenUserInformationActionClick = onOpenUserInformationActionClick; } - // void onOpenSearchMailActionClick(OnOpenSearchMailActionClick onOpenSearchMailActionClick) { - // _onOpenSearchMailActionClick = onOpenSearchMailActionClick; - // } + void onOpenSearchMailActionClick(OnOpenSearchMailActionClick onOpenSearchMailActionClick) { + _onOpenSearchMailActionClick = onOpenSearchMailActionClick; + } void onOpenListMailboxActionClick(OnOpenListMailboxActionClick onOpenListMailboxActionClick) { _onOpenListMailboxActionClick = onOpenListMailboxActionClick; @@ -47,8 +47,10 @@ class AppBarThreadWidgetBuilder { Widget build() { return Container( key: Key('app_bar_thread_widget'), - alignment: Alignment.center, - padding: EdgeInsets.only(top: 16, bottom: 8), + alignment: Alignment.topCenter, + color: Colors.white, + margin: EdgeInsets.zero, + padding: EdgeInsets.only(left: 16, top: 16, bottom: 8, right: 16), child: MediaQuery( data: MediaQueryData(padding: EdgeInsets.zero), child: Row( @@ -57,7 +59,7 @@ class AppBarThreadWidgetBuilder { children: [ if (_responsiveUtils.isMobile(_context)) _buildIconUser(), Expanded(child: _buildContentCenterAppBar()), - // _buildIconSearch(), + _buildIconSearch(), ] ) ) @@ -78,19 +80,24 @@ class AppBarThreadWidgetBuilder { .build())); } - // Widget _buildIconSearch() { - // return Padding( - // padding: EdgeInsets.only(left: 16), - // child: IconButton( - // color: AppColor.baseTextColor, - // icon: SvgPicture.asset(_imagePaths.icSearch, color: AppColor.baseTextColor, fit: BoxFit.fill), - // onPressed: () => { - // if (_onOpenSearchMailActionClick != null) { - // _onOpenSearchMailActionClick!() - // } - // } - // )); - // } + Widget _buildIconSearch() { + return Material( + borderRadius: BorderRadius.circular(12), + color: Colors.transparent, + child: Padding( + padding: EdgeInsets.only(left: 16), + child: IconButton( + color: AppColor.baseTextColor, + icon: SvgPicture.asset(_imagePaths.icSearch, color: AppColor.baseTextColor, fit: BoxFit.fill), + onPressed: () => { + if (_onOpenSearchMailActionClick != null) { + _onOpenSearchMailActionClick!() + } + } + ) + ) + ); + } Widget _buildContentCenterAppBar() { return Row( diff --git a/lib/features/thread/presentation/widgets/email_tile_builder.dart b/lib/features/thread/presentation/widgets/email_tile_builder.dart index 66143f4c8..fd913d513 100644 --- a/lib/features/thread/presentation/widgets/email_tile_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_builder.dart @@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart'; typedef OnOpenEmailActionClick = void Function(PresentationEmail selectedEmail); typedef OnSelectEmailActionClick = void Function(PresentationEmail selectedEmail); @@ -20,6 +22,8 @@ class EmailTileBuilder { final ResponsiveUtils _responsiveUtils; final SelectMode _selectModeAll; final Role? _mailboxRole; + final SearchStatus _searchStatus; + final SearchQuery? _searchQuery; OnOpenEmailActionClick? _onOpenEmailActionClick; OnSelectEmailActionClick? _onSelectEmailActionClick; @@ -33,6 +37,8 @@ class EmailTileBuilder { this._responsiveUtils, this._mailboxRole, this._selectModeAll, + this._searchStatus, + this._searchQuery, ); void onOpenEmailAction(OnOpenEmailActionClick onOpenEmailActionClick) { @@ -84,18 +90,28 @@ class EmailTileBuilder { mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( - child: Text( - (_mailboxRole == PresentationMailbox.roleSent - || _mailboxRole == PresentationMailbox.roleDrafts - || _mailboxRole == PresentationMailbox.roleOutbox) - ? '${_presentationEmail.recipientsName()}' - : '${_presentationEmail.getSenderName()}', - maxLines: 1, - overflow:TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14, - color: AppColor.mailboxTextColor, - fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500))), + child: _searchStatus == SearchStatus.ACTIVE && _searchQuery != null && _searchQuery!.value.isNotEmpty + ? RichTextBuilder( + _getInformationSender(), + '${_searchQuery!.value}', + TextStyle( + fontSize: 14, + color: AppColor.mailboxTextColor, + fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500), + TextStyle( + fontSize: 14, + color: AppColor.mailboxTextColor, + backgroundColor: AppColor.bgWordSearch, + fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500)).build() + : Text( + _getInformationSender(), + maxLines: 1, + overflow:TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: AppColor.mailboxTextColor, + fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500)) + ), Padding( padding: EdgeInsets.only(right: 4), child: Text( @@ -116,24 +132,65 @@ class EmailTileBuilder { children: [ Padding( padding: EdgeInsets.only(bottom: 3), - child: Text( - '${_presentationEmail.getEmailTitle()}', - maxLines: 1, - overflow:TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - color: _presentationEmail.isUnReadEmail() ? AppColor.subjectEmailTextColorUnRead : AppColor.baseTextColor, - fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500))), + child: Row( + children: [ + Expanded( + child: _searchStatus == SearchStatus.ACTIVE && _searchQuery != null && _searchQuery!.value.isNotEmpty + ? RichTextBuilder( + '${_presentationEmail.getEmailTitle()}', + '${_searchQuery!.value}', + TextStyle( + fontSize: 12, + color: _presentationEmail.isUnReadEmail() ? AppColor.subjectEmailTextColorUnRead : AppColor.baseTextColor, + fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500), + TextStyle( + fontSize: 12, + backgroundColor: AppColor.bgWordSearch, + color: _presentationEmail.isUnReadEmail() ? AppColor.subjectEmailTextColorUnRead : AppColor.baseTextColor, + fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500)).build() + : Text( + '${_presentationEmail.getEmailTitle()}', + maxLines: 1, + overflow:TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: _presentationEmail.isUnReadEmail() ? AppColor.subjectEmailTextColorUnRead : AppColor.baseTextColor, + fontWeight: _presentationEmail.isUnReadEmail() ? FontWeight.bold : FontWeight.w500)) + ), + if (_searchStatus == SearchStatus.ACTIVE && _presentationEmail.mailboxName.isNotEmpty) + Container( + margin: EdgeInsets.only(left: 8), + padding: EdgeInsets.only(left: 8, right: 8, top: 3, bottom: 3), + constraints: BoxConstraints(maxWidth: 100), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: AppColor.backgroundCounterMailboxColor), + child: Text( + '${_presentationEmail.mailboxName}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 10, color: AppColor.mailboxTextColor, fontWeight: FontWeight.bold), + ) + ) + ], + )), Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( - child: Text( - '${_presentationEmail.getPartialContent()}', - maxLines: 1, - overflow:TextOverflow.ellipsis, - style: TextStyle(fontSize: 12, color: AppColor.baseTextColor))), + child: _searchStatus == SearchStatus.ACTIVE && _searchQuery != null && _searchQuery!.value.isNotEmpty + ? RichTextBuilder( + '${_presentationEmail.getPartialContent()}', + '${_searchQuery!.value}', + TextStyle(fontSize: 12, color: AppColor.baseTextColor), + TextStyle(fontSize: 12, color: AppColor.baseTextColor, backgroundColor: AppColor.bgWordSearch)).build() + : Text( + '${_presentationEmail.getPartialContent()}', + maxLines: 1, + overflow:TextOverflow.ellipsis, + style: TextStyle(fontSize: 12, color: AppColor.baseTextColor)) + ), if (_presentationEmail.hasAttachment == true) (ButtonBuilder(_imagePaths.icShare) ..padding(5) @@ -224,4 +281,13 @@ class EmailTileBuilder { } } } + + String _getInformationSender() { + if (_mailboxRole == PresentationMailbox.roleSent + || _mailboxRole == PresentationMailbox.roleDrafts + || _mailboxRole == PresentationMailbox.roleOutbox) { + return '${_presentationEmail.recipientsName()}'; + } + return '${_presentationEmail.getSenderName()}'; + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/search_app_bar_widget.dart b/lib/features/thread/presentation/widgets/search_app_bar_widget.dart new file mode 100644 index 000000000..9d9745d47 --- /dev/null +++ b/lib/features/thread/presentation/widgets/search_app_bar_widget.dart @@ -0,0 +1,164 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnCancelSearchPressed = Function(); +typedef OnClearTextSearchAction = Function(); +typedef OnSuggestionSearchQuery = Function(String); +typedef OnSearchTextAction = Function(String); + +class SearchAppBarWidget { + OnCancelSearchPressed? _onCancelSearchPressed; + OnSuggestionSearchQuery? _onSuggestionSearchQuery; + OnClearTextSearchAction? _onClearTextSearchAction; + OnSearchTextAction? _onSearchTextAction; + + final BuildContext _context; + final ImagePaths _imagePaths; + final SearchQuery? _searchQuery; + final TextEditingController? _searchInputController; + final FocusNode? _searchFocusNode; + final List? _suggestionSearch; + + double? _heightSearchBar; + Decoration? _decoration; + EdgeInsets? _padding; + + SearchAppBarWidget( + this._context, + this._imagePaths, + this._searchQuery, + this._suggestionSearch, + this._searchFocusNode, + this._searchInputController, + ); + + void addOnCancelSearchPressed(OnCancelSearchPressed onCancelSearchPressed) { + _onCancelSearchPressed = onCancelSearchPressed; + } + + void addOnSuggestionSearchQuery(OnSuggestionSearchQuery onSuggestionSearchQuery) { + _onSuggestionSearchQuery = onSuggestionSearchQuery; + } + + void addOnClearTextSearchAction(OnClearTextSearchAction onClearTextSearchAction) { + _onClearTextSearchAction = onClearTextSearchAction; + } + + void addOnSearchTextAction(OnSearchTextAction onSearchTextAction) { + _onSearchTextAction = onSearchTextAction; + } + + void setHeightSearchBar(double heightSearchBar) { + _heightSearchBar = heightSearchBar; + } + + void addDecoration(Decoration decoration) { + _decoration = decoration; + } + + void addPadding(EdgeInsets padding) { + _padding = padding; + } + + Widget build() { + return Container( + key: Key('search_app_bar_widget'), + alignment: Alignment.topCenter, + height: _heightSearchBar, + decoration: _decoration, + padding: _padding ?? EdgeInsets.symmetric(vertical: 8), + child: MediaQuery( + data: MediaQueryData(padding: EdgeInsets.zero), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildBackButton(), + Expanded(child: _buildSearchInputForm()), + if (_suggestionSearch?.isNotEmpty == true || (_searchQuery != null && _searchQuery!.value.isNotEmpty)) + _buildClearTextSearchButton(), + ] + ) + ) + ); + } + + Widget _buildBackButton() { + return Material( + borderRadius: BorderRadius.circular(12), + color: Colors.transparent, + child: Padding( + padding: EdgeInsets.only(left: 8), + child: IconButton( + color: AppColor.appColor, + icon: SvgPicture.asset(_imagePaths.icBack, color: AppColor.appColor, fit: BoxFit.fill), + onPressed: () { + _searchInputController?.clear(); + if (_onCancelSearchPressed != null) { + _onCancelSearchPressed!(); + } + } + ))); + } + + Widget _buildClearTextSearchButton() { + return Material( + borderRadius: BorderRadius.circular(12), + color: Colors.transparent, + child: Padding( + padding: EdgeInsets.only(right: 8), + child: IconButton( + color: AppColor.baseTextColor, + icon: SvgPicture.asset(_imagePaths.icComposerClose, color: AppColor.baseTextColor, fit: BoxFit.fill), + onPressed: () { + _searchInputController?.clear(); + if (_onClearTextSearchAction != null) { + _onClearTextSearchAction!(); + } + } + ))); + } + + Widget _buildSearchInputForm() { + return (TextFieldBuilder() + ..key(Key('search_input_form')) + ..textInputAction(TextInputAction.done) + ..onChange((value) { + if (_onSuggestionSearchQuery != null) { + _onSuggestionSearchQuery!(value); + }}) + ..onSubmitted((value) { + if (_onSearchTextAction != null) { + _onSearchTextAction!(value); + }}) + ..cursorColor(AppColor.baseTextColor) + ..autoFocus(true) + ..addFocusNode(_searchFocusNode) + ..textStyle(TextStyle( + color: _searchQuery != null && _searchQuery!.value.isNotEmpty && _suggestionSearch?.isEmpty == true + ? AppColor.nameUserColor + : AppColor.baseTextColor, + fontSize: 15, + fontWeight: FontWeight.w500)) + ..textDecoration(InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 15), + hintText: AppLocalizations.of(_context).search_mail, + hintStyle: TextStyle( + color: AppColor.baseTextColor, + fontWeight: FontWeight.w500, + fontSize: 15.0), + labelStyle: TextStyle( + color: AppColor.baseTextColor, + fontWeight: FontWeight.w500, + fontSize: 15.0))) + ..addController(_searchInputController)) + .build(); + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/suggestion_box_widget.dart b/lib/features/thread/presentation/widgets/suggestion_box_widget.dart new file mode 100644 index 000000000..f9bd2237a --- /dev/null +++ b/lib/features/thread/presentation/widgets/suggestion_box_widget.dart @@ -0,0 +1,88 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnSelectedSuggestion = Function(String); + +class SuggestionBoxWidget { + + OnSelectedSuggestion? _onSelectedSuggestion; + + final BuildContext _context; + final ImagePaths _imagePaths; + + double? _suggestionHeight; + double? _elevation; + List _listData; + + SuggestionBoxWidget( + this._context, + this._imagePaths, + this._listData, + ); + + void setSuggestionHeight(double suggestionHeight) { + _suggestionHeight = suggestionHeight; + } + + void setElevation(double elevation) { + _elevation = elevation; + } + + void addOnSelectedSuggestion(OnSelectedSuggestion onSelectedSuggestion) { + _onSelectedSuggestion = onSelectedSuggestion; + } + + Widget build() { + return Material( + elevation: _elevation ?? 3, + color: Colors.white, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: _suggestionHeight ?? 60, + ), + child: ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: _listData.length, + itemBuilder: (BuildContext context, int index) => ListTile( + contentPadding: EdgeInsets.symmetric(horizontal: 10), + onTap: () { + if (_onSelectedSuggestion != null) { + _onSelectedSuggestion!(_listData[index]); + }}, + leading: IconButton( + color: AppColor.baseTextColor, + icon: SvgPicture.asset(_imagePaths.icSearch, color: AppColor.baseTextColor, fit: BoxFit.fill), + onPressed: () { + if (_onSelectedSuggestion != null) { + _onSelectedSuggestion!(_listData[index]); + } + }), + title: Transform( + transform: Matrix4.translationValues(-12.0, 0.0, 0.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + '${AppLocalizations.of(_context).prefix_suggestion_search} ', + style: TextStyle(fontSize: 15, color: AppColor.baseTextColor), + ), + Expanded(child: Text( + '"${_listData[index]}"', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 15, color: AppColor.nameUserColor, fontWeight: FontWeight.w500), + )) + ], + ) + ) + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0605507e7..c0f557605 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -397,5 +397,29 @@ "placeholders": { "count": {} } + }, + "search_mail": "Search mail", + "@search_mail": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "prefix_suggestion_search": "Search for", + "@prefix_suggestion_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "no_emails_matching_your_search": "No emails are matching your search", + "@no_emails_matching_your_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "results": "Results", + "@results": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 0605507e7..c0f557605 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -397,5 +397,29 @@ "placeholders": { "count": {} } + }, + "search_mail": "Search mail", + "@search_mail": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "prefix_suggestion_search": "Search for", + "@prefix_suggestion_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "no_emails_matching_your_search": "No emails are matching your search", + "@no_emails_matching_your_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "results": "Results", + "@results": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 0605507e7..e1e5af807 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2021-10-13T22:37:55.795262", + "@@last_modified": "2021-10-26T14:38:31.924635", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -397,5 +397,29 @@ "placeholders": { "count": {} } + }, + "search_mail": "Search mail", + "@search_mail": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "prefix_suggestion_search": "Search for", + "@prefix_suggestion_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "no_emails_matching_your_search": "No emails are matching your search", + "@no_emails_matching_your_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "results": "Results", + "@results": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 0605507e7..c0f557605 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -397,5 +397,29 @@ "placeholders": { "count": {} } + }, + "search_mail": "Search mail", + "@search_mail": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "prefix_suggestion_search": "Search for", + "@prefix_suggestion_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "no_emails_matching_your_search": "No emails are matching your search", + "@no_emails_matching_your_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "results": "Results", + "@results": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 0605507e7..c0f557605 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -397,5 +397,29 @@ "placeholders": { "count": {} } + }, + "search_mail": "Search mail", + "@search_mail": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "prefix_suggestion_search": "Search for", + "@prefix_suggestion_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "no_emails_matching_your_search": "No emails are matching your search", + "@no_emails_matching_your_search": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "results": "Results", + "@results": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 69ac422ee..299e77355 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -431,5 +431,33 @@ class AppLocalizations { args: [count] ); } + + String get search_mail { + return Intl.message( + 'Search mail', + name: 'search_mail', + ); + } + + String get prefix_suggestion_search { + return Intl.message( + 'Search for', + name: 'prefix_suggestion_search', + ); + } + + String get no_emails_matching_your_search { + return Intl.message( + 'No emails are matching your search', + name: 'no_emails_matching_your_search', + ); + } + + String get results { + return Intl.message( + 'Results', + name: 'results', + ); + } }