diff --git a/assets/images/ic_filter_message_all.svg b/assets/images/ic_filter_message_all.svg new file mode 100644 index 000000000..d27976256 --- /dev/null +++ b/assets/images/ic_filter_message_all.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_filter_message_attachments.svg b/assets/images/ic_filter_message_attachments.svg new file mode 100644 index 000000000..f1ee80599 --- /dev/null +++ b/assets/images/ic_filter_message_attachments.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_filter_selected.svg b/assets/images/ic_filter_selected.svg new file mode 100644 index 000000000..2c8236226 --- /dev/null +++ b/assets/images/ic_filter_selected.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/lib/core.dart b/core/lib/core.dart index bcbe072bb..94a42cf1c 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -42,6 +42,8 @@ export 'presentation/views/dialog/downloading_file_dialog_builder.dart'; export 'presentation/views/background/background_widget_builder.dart'; export 'presentation/views/html_viewer/html_content_viewer_widget.dart'; export 'presentation/views/floating_button/scrolling_floating_button_animated.dart'; +export 'presentation/views/bottom_popup/cupertino_action_sheet_action_builder.dart'; +export 'presentation/views/bottom_popup/cupertino_action_sheet_builder.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 3f7d6ac31..c8253067e 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -71,6 +71,9 @@ extension AppColor on Color { static const colorDividerMailbox = Color(0xFF99A2AD); static const colorArrowUserMailbox = Color(0xFFB8C1CC); static const colorBgMailbox = Color(0xFFF7F7F7); + static const colorFilterMessageDisabled = Color(0xFF99A2AD); + static const colorFilterMessageEnabled = Color(0xFF007AFF); + static const colorDefaultCupertinoActionSheet = Color(0x66000000); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 5758855e8..e166628d6 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -75,6 +75,9 @@ class ImagePaths { String get icMailboxArchivedV2 => _getImagePath('ic_mailbox_archived_v2.svg'); String get icMailboxSpamV2 => _getImagePath('ic_mailbox_spam_v2.svg'); String get icMailboxTrashV2 => _getImagePath('ic_mailbox_trash_v2.svg'); + String get icFilterSelected => _getImagePath('ic_filter_selected.svg'); + String get icFilterMessageAll => _getImagePath('ic_filter_message_all.svg'); + String get icFilterMessageAttachments => _getImagePath('ic_filter_message_attachments.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/utils/responsive_utils.dart b/core/lib/presentation/utils/responsive_utils.dart index 2acb28039..80b2d97a2 100644 --- a/core/lib/presentation/utils/responsive_utils.dart +++ b/core/lib/presentation/utils/responsive_utils.dart @@ -40,7 +40,12 @@ class ResponsiveUtils { double getWidthLoginButton() => _loginButtonWidth; EdgeInsets getMarginDestinationPicker(BuildContext context) { - if (isTablet(context)) { + if (isMobileDevice(context) && isLandscape(context)) { + return EdgeInsets.only( + left: _tabletHorizontalMargin, + right: _tabletHorizontalMargin, + top: 50.0); + } else if (isTablet(context)) { return getSizeHeightScreen(context) <= _tabletVerticalMargin * 2 ? EdgeInsets.symmetric( horizontal: _tabletHorizontalMargin, @@ -62,7 +67,9 @@ class ResponsiveUtils { } BorderRadius radiusDestinationPicker(BuildContext context, double radius) { - if (isTablet(context)) { + if (isMobileDevice(context) && isLandscape(context)) { + return BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)); + } else if (isTablet(context)) { return getSizeHeightScreen(context) <= _tabletVerticalMargin * 2 ? BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)) : BorderRadius.circular(radius); diff --git a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_action_builder.dart b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_action_builder.dart new file mode 100644 index 000000000..f2d143297 --- /dev/null +++ b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_action_builder.dart @@ -0,0 +1,29 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +typedef OnCupertinoActionSheetActionClick = void Function(T data); + +abstract class CupertinoActionSheetActionBuilder { + @protected final Key key; + @protected final SvgPicture actionIcon; + @protected final String actionName; + @protected OnCupertinoActionSheetActionClick? onCupertinoActionSheetActionClick; + + CupertinoActionSheetActionBuilder( + this.key, + this.actionIcon, + this.actionName + ); + + void onActionClick(OnCupertinoActionSheetActionClick onCupertinoActionSheetActionClick) { + this.onCupertinoActionSheetActionClick = onCupertinoActionSheetActionClick; + } + + TextStyle actionTextStyle() { + return TextStyle(fontSize: 17, color: AppColor.colorNameEmail); + } + + Widget build(); +} \ No newline at end of file diff --git a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart new file mode 100644 index 000000000..83d3c829a --- /dev/null +++ b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart @@ -0,0 +1,44 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; + +class CupertinoActionSheetBuilder { + + final BuildContext _context; + final List _actionTiles = []; + + Widget? _titleWidget; + Widget? _messageWidget; + Widget? _cancelWidget; + + CupertinoActionSheetBuilder(this._context); + + void title(Widget? titleWidget) { + _titleWidget = titleWidget; + } + + void message(Widget? messageWidget) { + _messageWidget = messageWidget; + } + + void addCancelButton(Widget? cancelWidget) { + _cancelWidget = cancelWidget; + } + + void addTiles(List tiles) { + _actionTiles.addAll(tiles); + } + + void build() { + showCupertinoModalPopup( + context: _context, + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + builder: (context) => CupertinoActionSheet( + title: _titleWidget, + message: _messageWidget, + actions: _actionTiles, + cancelButton: _cancelWidget, + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart b/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart index 6315f5abf..77c9777d3 100644 --- a/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart +++ b/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart @@ -24,7 +24,9 @@ class AppBarDestinationPickerBuilder { key: Key('app_bar_destination_picker'), alignment: Alignment.center, padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), - color: Colors.white, + decoration: BoxDecoration( + borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)), + color: Colors.white), child: MediaQuery( data: MediaQueryData(padding: EdgeInsets.zero), child: Row( diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index b4b592a20..09115c1d4 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -48,6 +48,7 @@ class EmailController extends BaseController { final emailContents = [].obs; final attachments = [].obs; EmailId? _currentEmailId; + List? _currentAvatarColor; EmailController( this._getEmailContentInteractor, @@ -66,6 +67,7 @@ class EmailController extends BaseController { mailboxDashBoardController.selectedEmail.listen((presentationEmail) { if (_currentEmailId != presentationEmail?.id) { _currentEmailId = presentationEmail?.id; + _currentAvatarColor = presentationEmail?.avatarColors; _clearEmailContent(); final accountId = mailboxDashBoardController.accountId.value; if (accountId != null && presentationEmail != null) { @@ -151,7 +153,7 @@ class EmailController extends BaseController { void _markAsEmailReadSuccess(Success success) { if (success is MarkAsEmailReadSuccess) { - mailboxDashBoardController.setSelectedEmail(success.updatedEmail.toPresentationEmail(selectMode: SelectMode.ACTIVE)); + mailboxDashBoardController.setSelectedEmail(success.updatedEmail.toPresentationEmail(selectMode: SelectMode.ACTIVE, avatarColors: _currentAvatarColor)); } mailboxDashBoardController.dispatchState(Right(success)); diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 6dfd6e49e..a5f707aa8 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -83,10 +83,10 @@ class EmailView extends GetView { width: double.infinity, alignment: Alignment.center, padding: EdgeInsets.only( - left: responsiveUtils.isMobile(context) ? 16 : 24, - right: responsiveUtils.isMobile(context) ? 16 : 24, - bottom: responsiveUtils.isMobile(context) ? 16 : 24, - top: responsiveUtils.isMobile(context) ? 10 : 16), + left: 16, + right: 16, + bottom: 16, + top: 16), color: Colors.white, child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -120,9 +120,9 @@ class EmailView extends GetView { Widget _buildEmailMessage(BuildContext context) { return Container( padding: EdgeInsets.only( - bottom: responsiveUtils.isMobile(context) ? 16 : 24, - left: responsiveUtils.isMobile(context) ? 16 : 24, - right: responsiveUtils.isMobile(context) ? 16 : 24, + bottom: 16, + left: 16, + right: 16, top: 10), alignment: Alignment.center, decoration: BoxDecoration( diff --git a/lib/features/email/presentation/widgets/email_content_item_builder.dart b/lib/features/email/presentation/widgets/email_content_item_builder.dart index ea595270d..c928e90bb 100644 --- a/lib/features/email/presentation/widgets/email_content_item_builder.dart +++ b/lib/features/email/presentation/widgets/email_content_item_builder.dart @@ -42,7 +42,7 @@ class EmailContentItemBuilder { padding: EdgeInsets.zero, child: Text( _emailContent.content, - style: TextStyle(fontSize: 14, color: AppColor.nameUserColor))); + style: TextStyle(fontSize: 14, color: AppColor.colorNameEmail))); case EmailContentType.other: return SizedBox.shrink(); } diff --git a/lib/features/thread/presentation/model/filter_message_option.dart b/lib/features/thread/presentation/model/filter_message_option.dart new file mode 100644 index 000000000..44e2ab09b --- /dev/null +++ b/lib/features/thread/presentation/model/filter_message_option.dart @@ -0,0 +1,67 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +enum FilterMessageOption { + all, + unread, + attachments, + starred, +} + +extension FilterMessageOptionExtension on FilterMessageOption { + + bool filterEmail(PresentationEmail email) { + switch(this) { + case FilterMessageOption.all: + return true; + case FilterMessageOption.unread: + return email.isUnReadEmail(); + case FilterMessageOption.attachments: + return email.withAttachments; + case FilterMessageOption.starred: + return email.isFlaggedEmail(); + } + } + + String getIconToast(ImagePaths imagePaths) { + switch(this) { + case FilterMessageOption.all: + return imagePaths.icFilterMessageAll; + case FilterMessageOption.unread: + return imagePaths.icUnreadToast; + case FilterMessageOption.attachments: + return imagePaths.icFilterMessageAttachments; + case FilterMessageOption.starred: + return imagePaths.icStar; + } + } + + String getMessageToast(BuildContext context) { + switch(this) { + case FilterMessageOption.all: + return AppLocalizations.of(context).disable_filter_message_toast; + case FilterMessageOption.unread: + return AppLocalizations.of(context).filter_message_toast(AppLocalizations.of(context).unread); + case FilterMessageOption.attachments: + return AppLocalizations.of(context).filter_message_toast(AppLocalizations.of(context).with_attachments); + case FilterMessageOption.starred: + return AppLocalizations.of(context).filter_message_toast(AppLocalizations.of(context).starred); + } + } + + String getTitle(BuildContext context) { + switch(this) { + case FilterMessageOption.all: + return ''; + case FilterMessageOption.unread: + return AppLocalizations.of(context).with_unread; + case FilterMessageOption.attachments: + return AppLocalizations.of(context).with_attachments.capitalizeFirstEach; + case FilterMessageOption.starred: + return AppLocalizations.of(context).with_starred; + } + } +} \ 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 525f31074..310ae54ae 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -39,6 +39,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multi 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/domain/usecases/search_more_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/filter_message_option.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'; @@ -63,7 +64,9 @@ class ThreadController extends BaseController { final emailList = [].obs; final emailListSearch = [].obs; + final emailListFiltered = [].obs; final currentSelectMode = SelectMode.INACTIVE.obs; + final filterMessageOption = FilterMessageOption.all.obs; final random = Random(); @@ -204,7 +207,8 @@ class ThreadController extends BaseController { void _resetToOriginalValue() { dispatchState(Right(LoadingState())); - emailList.value = []; + emailList.clear(); + emailListFiltered.clear(); canLoadMore = true; disableSearch(); cancelSelectEmail(); @@ -216,6 +220,12 @@ class ThreadController extends BaseController { .map((email) => email.asAvatarGradientColor(random)) .toList(); emailList.value = listEmailHaveAvatarGradientColor; + if (isFilterMessagesEnabled) { + final emailsFiltered = listEmailHaveAvatarGradientColor + .where((email) => filterMessageOption.value.filterEmail(email)) + .toList(); + emailListFiltered.addAll(emailsFiltered); + } } void _getAllEmailAction(AccountId accountId, {MailboxId? mailboxId}) { @@ -283,6 +293,13 @@ class ThreadController extends BaseController { .map((email) => email.asAvatarGradientColor(random)) .toList(); emailList.addAll(listEmailHaveAvatarGradientColor); + + if (isFilterMessagesEnabled) { + final emailsFilteredMore = listEmailHaveAvatarGradientColor + .where((email) => filterMessageOption.value.filterEmail(email)) + .toList(); + emailListFiltered.addAll(emailsFilteredMore); + } } else { canLoadMore = false; } @@ -307,9 +324,15 @@ class ThreadController extends BaseController { .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 (isFilterMessagesEnabled) { + emailListFiltered.value = emailListFiltered + .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()) { @@ -329,7 +352,11 @@ class ThreadController extends BaseController { if (isSearchActive()) { return emailListSearch.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); } else { - return emailList.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); + if (isFilterMessagesEnabled) { + return emailListFiltered.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); + } else { + return emailList.where((email) => email.selectMode == SelectMode.ACTIVE).toList(); + } } } @@ -337,7 +364,11 @@ class ThreadController extends BaseController { if (isSearchActive()) { return emailListSearch.every((email) => email.selectMode == SelectMode.INACTIVE); } else { - return emailList.every((email) => email.selectMode == SelectMode.INACTIVE); + if (isFilterMessagesEnabled) { + return emailListFiltered.every((email) => email.selectMode == SelectMode.INACTIVE); + } else { + return emailList.every((email) => email.selectMode == SelectMode.INACTIVE); + } } } @@ -349,7 +380,11 @@ class ThreadController extends BaseController { 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(); + if (isFilterMessagesEnabled) { + emailListFiltered.value = emailListFiltered.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); + } else { + emailList.value = emailList.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); + } } currentSelectMode.value = SelectMode.INACTIVE; } @@ -397,6 +432,35 @@ class ThreadController extends BaseController { _appToast.showErrorToast(AppLocalizations.of(Get.context!).an_error_occurred); } + void openFilterMessagesCupertinoActionSheet(BuildContext context, List actionTiles, {Widget? cancelButton}) { + (CupertinoActionSheetBuilder(context) + ..addTiles(actionTiles) + ..addCancelButton(cancelButton)) + .build(); + } + + void closeFilterMessageActionSheet() { + popBack(); + } + + bool get isFilterMessagesEnabled => filterMessageOption != FilterMessageOption.all; + + void filterMessagesAction(BuildContext context, FilterMessageOption filterOption) { + popBack(); + + final newFilterOption = filterMessageOption.value == filterOption ? FilterMessageOption.all : filterOption; + + final emailsFiltered = emailList.where((email) => newFilterOption.filterEmail(email)).toList(); + emailListFiltered.value = emailsFiltered; + + filterMessageOption.value = newFilterOption; + + _appToast.showToastWithIcon( + Get.overlayContext!, + message: newFilterOption.getMessageToast(context), + icon: newFilterOption.getIconToast(_imagePaths)); + } + void openContextMenuSelectedEmail(BuildContext context, List actionTiles) { (ContextMenuBuilder(context) ..addTiles(actionTiles)) @@ -573,7 +637,7 @@ class ThreadController extends BaseController { void _searchEmailsSuccess(SearchEmailSuccess success) { final resultEmailSearchList = success.emailList - .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailbox)) + .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailbox, random)) .toList(); emailListSearch.value = resultEmailSearchList; @@ -597,7 +661,7 @@ class ThreadController extends BaseController { void _searchMoreEmailsSuccess(SearchMoreEmailSuccess success) { if (success.emailList.isNotEmpty) { final resultEmailSearchList = success.emailList - .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailbox)) + .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailbox, random)) .where((email) => !emailListSearch.contains(email)) .toList(); emailListSearch.addAll(resultEmailSearchList); diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index d86b46dfd..11c911f12 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -1,14 +1,17 @@ import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.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/domain/state/search_more_email_state.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.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/bottom_bar_thread_selection_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_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/search_bar_thread_view_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/suggestion_box_widget.dart'; @@ -146,13 +149,72 @@ class ThreadView extends GetWidget { responsiveUtils, controller.mailboxDashBoardController.selectedMailbox.value, controller.getListEmailSelected(), - controller.currentSelectMode.value) + controller.currentSelectMode.value, + controller.filterMessageOption.value) ..addOpenListMailboxActionClick(() => controller.openMailboxLeftMenu()) ..addOnEditThreadAction(() => controller.enableSelectionEmail()) + ..addOnFilterEmailAction((filterMessageOption) => + controller.openFilterMessagesCupertinoActionSheet( + context, + _filterMessagesCupertinoActionTile(context, filterMessageOption), + cancelButton: _buildCupertinoActionCancelButton(context))) ..addOnCancelEditThread(() => controller.cancelSelectEmail())) .build(); } + List _filterMessagesCupertinoActionTile(BuildContext context, FilterMessageOption optionCurrent) { + return [ + _filterMessageWithAttachmentsAction(context, optionCurrent), + _filterMessagesUnreadAction(context, optionCurrent), + _filterMessageStarredAction(context, optionCurrent), + ]; + } + + Widget _buildCupertinoActionCancelButton(BuildContext context) { + return CupertinoActionSheetAction( + child: Text( + AppLocalizations.of(context).cancel, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorTextButton)), + onPressed: () => controller.closeFilterMessageActionSheet(), + ); + } + + Widget _filterMessageWithAttachmentsAction(BuildContext context, FilterMessageOption optionCurrent) { + return (FilterMessageCupertinoActionSheetActionBuilder( + Key('filter_attachment_action'), + SvgPicture.asset(imagePaths.icAttachment, width: 28, height: 28, fit: BoxFit.fill, color: AppColor.colorTextButton), + AppLocalizations.of(context).with_attachments, + FilterMessageOption.attachments, + optionCurrent: optionCurrent, + actionSelected: SvgPicture.asset(imagePaths.icFilterSelected, fit: BoxFit.fill)) + ..onActionClick((option) => controller.filterMessagesAction(context, option))) + .build(); + } + + Widget _filterMessagesUnreadAction(BuildContext context, FilterMessageOption optionCurrent) { + return (FilterMessageCupertinoActionSheetActionBuilder( + Key('filter_unread_action'), + SvgPicture.asset(imagePaths.icUnreadV2, width: 28, height: 28, fit: BoxFit.fill), + AppLocalizations.of(context).unread, + FilterMessageOption.unread, + optionCurrent: optionCurrent, + actionSelected: SvgPicture.asset(imagePaths.icFilterSelected, fit: BoxFit.fill)) + ..onActionClick((option) => controller.filterMessagesAction(context, option))) + .build(); + } + + Widget _filterMessageStarredAction(BuildContext context, FilterMessageOption optionCurrent) { + return (FilterMessageCupertinoActionSheetActionBuilder( + Key('filter_starred_action'), + SvgPicture.asset(imagePaths.icStar, width: 28, height: 28, fit: BoxFit.fill), + AppLocalizations.of(context).starred, + FilterMessageOption.starred, + optionCurrent: optionCurrent, + actionSelected: SvgPicture.asset(imagePaths.icFilterSelected, fit: BoxFit.fill)) + ..onActionClick((option) => controller.filterMessagesAction(context, option))) + .build(); + } + // Widget _buildAppBarSelectModeActive(BuildContext context) { // return (AppBarThreadSelectModeActiveBuilder( // context, @@ -318,7 +380,9 @@ class ThreadView extends GetWidget { return _buildResultSearchEmails(context, controller.emailListSearch); } } else { - return _buildResultListEmail(context, controller.emailList); + return _buildResultListEmail( + context, + controller.isFilterMessagesEnabled ? controller.emailListFiltered : controller.emailList); } }) ); 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 23c64ef46..52b6c280d 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 @@ -4,9 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/filter_message_option.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -typedef OnFilterEmailAction = void Function(); +typedef OnFilterEmailAction = void Function(FilterMessageOption); typedef OnEditThreadAction = void Function(); typedef OnOpenListMailboxActionClick = void Function(); typedef OnCancelEditThread = void Function(); @@ -23,6 +24,7 @@ class AppBarThreadWidgetBuilder { final PresentationMailbox? _presentationMailbox; final List _listSelectionEmail; final SelectMode _selectMode; + final FilterMessageOption _filterMessageOption; AppBarThreadWidgetBuilder( this._context, @@ -31,6 +33,7 @@ class AppBarThreadWidgetBuilder { this._presentationMailbox, this._listSelectionEmail, this._selectMode, + this._filterMessageOption, ); void addOnFilterEmailAction(OnFilterEmailAction onFilterEmailAction) { @@ -69,9 +72,20 @@ class AppBarThreadWidgetBuilder { if (_selectMode == SelectMode.ACTIVE) Positioned(left: 40, child: _buildCountItemSelected()), Positioned(right: 0, child: _buildFilterButton()), - Padding( - padding: EdgeInsets.only(left: 60, right: 40), - child: _buildContentCenterAppBar()), + _filterMessageOption.getTitle(_context).isNotEmpty + ? Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Padding( + padding: EdgeInsets.only(left: 60, right: 40), + child: _buildContentCenterAppBar()), + Transform( + transform: Matrix4.translationValues(-2.0, -8.0, 0.0), + child: Text( + _filterMessageOption.getTitle(_context), + style: TextStyle(fontSize: 11, color: AppColor.colorContentEmail))) + ]) + : Padding( + padding: EdgeInsets.only(left: 60, right: 40), + child: _buildContentCenterAppBar()), ] )) ]) @@ -103,9 +117,13 @@ class AppBarThreadWidgetBuilder { child: Padding( padding: EdgeInsets.only(left: 16), child: IconButton( - color: AppColor.baseTextColor, - icon: SvgPicture.asset(_imagePaths.icFilter, fit: BoxFit.fill), - onPressed: () => _onFilterEmailAction?.call() + icon: SvgPicture.asset( + _imagePaths.icFilter, + color: _filterMessageOption == FilterMessageOption.all + ? AppColor.colorFilterMessageDisabled + : AppColor.colorFilterMessageEnabled, + fit: BoxFit.fill), + onPressed: () => _onFilterEmailAction?.call(_filterMessageOption) ) ) ); diff --git a/lib/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_builder.dart b/lib/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_builder.dart new file mode 100644 index 000000000..ec6d5eba1 --- /dev/null +++ b/lib/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_builder.dart @@ -0,0 +1,51 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/filter_message_option.dart'; + +class FilterMessageCupertinoActionSheetActionBuilder extends CupertinoActionSheetActionBuilder { + + final FilterMessageOption option; + final FilterMessageOption? optionCurrent; + final SvgPicture? actionSelected; + final Color? bgColor; + + FilterMessageCupertinoActionSheetActionBuilder( + Key key, + SvgPicture actionIcon, + String actionName, + this.option, + { + this.optionCurrent, + this.actionSelected, + this.bgColor, + } + ) : super(key, actionIcon, actionName); + + @override + Widget build() { + return Container( + color: bgColor ?? Colors.white, + child: CupertinoActionSheetAction( + key: key, + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Padding( + padding: EdgeInsets.only(left: 12, right: 16), + child: actionIcon), + Expanded(child: Text(actionName, textAlign: TextAlign.left, style: actionTextStyle())), + if (optionCurrent == option && actionSelected != null) + Padding( + padding: EdgeInsets.only(right: 12), + child: actionSelected!), + ]), + onPressed: () { + if (onCupertinoActionSheetActionClick != null) { + onCupertinoActionSheetActionClick!(option); + } + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 50c07ff02..f71d6b1b9 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -544,4 +544,45 @@ class AppLocalizations { 'Search for mailboxes', name: 'hint_search_mailboxes'); } + + String get with_attachments { + return Intl.message( + 'With attachments', + name: 'with_attachments' + ); + } + + String get starred { + return Intl.message( + 'Starred', + name: 'starred' + ); + } + + String filter_message_toast(String filterOption) { + return Intl.message( + 'You’ve filtered messages by "$filterOption"', + name: 'filter_message_toast', + args: [filterOption] + ); + } + + String get disable_filter_message_toast { + return Intl.message( + 'You’ve disabled filtered messages', + name: 'disable_filter_message_toast' + ); + } + + String get with_unread { + return Intl.message( + 'With Unread', + name: 'with_unread'); + } + + String get with_starred { + return Intl.message( + 'With Starred', + name: 'with_starred'); + } } \ No newline at end of file diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index a84d6a3f1..a8222d35e 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -90,6 +90,8 @@ class PresentationEmail with EquatableMixin { bool isFlaggedEmail() => keywords?.containsKey(KeyWordIdentifier.emailFlagged) == true; + bool get withAttachments => hasAttachment == true; + String get mailboxName => mailboxNames?.first?.name ?? ''; @override diff --git a/model/lib/extensions/email_extension.dart b/model/lib/extensions/email_extension.dart index 93cf44e8b..f6a52eb30 100644 --- a/model/lib/extensions/email_extension.dart +++ b/model/lib/extensions/email_extension.dart @@ -1,4 +1,6 @@ +import 'dart:ui'; + import 'package:http_parser/http_parser.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; @@ -41,7 +43,7 @@ extension EmailExtension on Email { ); } - PresentationEmail toPresentationEmail({SelectMode selectMode = SelectMode.INACTIVE}) { + PresentationEmail toPresentationEmail({SelectMode selectMode = SelectMode.INACTIVE, List? avatarColors}) { return PresentationEmail( id, keywords: keywords, @@ -57,7 +59,8 @@ extension EmailExtension on Email { bcc: bcc, replyTo: replyTo, mailboxIds: mailboxIds, - selectMode: selectMode + selectMode: selectMode, + avatarColors: avatarColors, ); } diff --git a/model/lib/extensions/presentation_email_extension.dart b/model/lib/extensions/presentation_email_extension.dart index 4d143ff4d..3264d6206 100644 --- a/model/lib/extensions/presentation_email_extension.dart +++ b/model/lib/extensions/presentation_email_extension.dart @@ -111,7 +111,7 @@ extension PresentationEmailExtension on PresentationEmail { } } - PresentationEmail toSearchPresentationEmail(Map mapMailboxes) { + PresentationEmail toSearchPresentationEmail(Map mapMailboxes, Random random) { mailboxIds?.removeWhere((key, value) => !value); final listMailboxId = mailboxIds?.entries @@ -124,6 +124,8 @@ extension PresentationEmailExtension on PresentationEmail { .where((mailboxName) => mailboxName != null) .toList(); + final newAvatarColors = AppColor.mapGradientColor[random.nextInt(AppColor.mapGradientColor.length)]; + return PresentationEmail( this.id, keywords: keywords, @@ -141,7 +143,7 @@ extension PresentationEmailExtension on PresentationEmail { mailboxIds: mailboxIds, mailboxNames: listMailboxName, selectMode: selectMode, - avatarColors: avatarColors + avatarColors: avatarColors ?? newAvatarColors ); }