From 1c7f9ad33380c059a02e72255dde1d4e72362675 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 5 Apr 2022 16:18:22 +0700 Subject: [PATCH] TF-239 Empty trash folder --- .../presentation/utils/responsive_utils.dart | 2 - lib/features/caching/email_cache_client.dart | 3 + .../data/datasource/email_datasource.dart | 3 +- .../email_datasource_impl.dart | 5 +- .../email/data/network/email_api.dart | 58 ++++-------- .../repository/email_repository_impl.dart | 5 +- .../domain/repository/email_repository.dart | 3 +- ...ultiple_emails_permanently_interactor.dart | 5 +- .../presentation/mailbox_controller.dart | 71 +++++++++------ .../data/local/email_cache_manager.dart | 5 ++ .../repository/thread_repository_impl.dart | 56 +++++++++++- .../domain/repository/thread_repository.dart | 2 + .../state/empty_trash_folder_state.dart | 18 ++++ .../empty_trash_folder_interactor.dart | 27 ++++++ .../thread/presentation/thread_bindings.dart | 6 +- .../presentation/thread_controller.dart | 47 +++++++++- .../thread/presentation/thread_view.dart | 46 +++++++++- lib/l10n/intl_messages.arb | 88 ++++++++++++++++++- lib/main/localizations/app_localizations.dart | 12 ++- 19 files changed, 367 insertions(+), 95 deletions(-) create mode 100644 lib/features/thread/domain/state/empty_trash_folder_state.dart create mode 100644 lib/features/thread/domain/usecases/empty_trash_folder_interactor.dart diff --git a/core/lib/presentation/utils/responsive_utils.dart b/core/lib/presentation/utils/responsive_utils.dart index f28ae3ec0..b6e1a3dfd 100644 --- a/core/lib/presentation/utils/responsive_utils.dart +++ b/core/lib/presentation/utils/responsive_utils.dart @@ -1,4 +1,3 @@ -import 'package:core/utils/app_logger.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; @@ -28,7 +27,6 @@ class ResponsiveUtils { double getDeviceWidth(BuildContext context) { final widthScreen = kIsWeb ? context.width : context.mediaQueryShortestSide; - log('ResponsiveUtils::getDeviceWidth(): widthScreen: $widthScreen'); return widthScreen; } diff --git a/lib/features/caching/email_cache_client.dart b/lib/features/caching/email_cache_client.dart index 03c72664b..32b81ec3e 100644 --- a/lib/features/caching/email_cache_client.dart +++ b/lib/features/caching/email_cache_client.dart @@ -1,4 +1,5 @@ +import 'package:core/core.dart'; import 'package:hive/hive.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_client.dart'; @@ -103,9 +104,11 @@ class EmailCacheClient extends HiveCacheClient { @override Future deleteMultipleItem(List listKey) { return Future.sync(() async { + log('EmailCacheClient::deleteMultipleItem(): listKey: ${listKey.length}'); final boxEmail = await openBox(); return boxEmail.deleteAll(listKey); }).catchError((error) { + log('EmailCacheClient::deleteMultipleItem(): error: $error'); throw error; }); } diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index b5ce6bd03..39129eafb 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; @@ -52,7 +51,7 @@ abstract class EmailDataSource { Future updateEmailDrafts(AccountId accountId, Email newEmail, EmailId oldEmailId); - Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds); + Future> deleteMultipleEmailsPermanently(AccountId accountId, List emailIds); Future deleteEmailPermanently(AccountId accountId, EmailId emailId); } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_datasource_impl.dart index 7e3f8c104..2b20366a7 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; @@ -135,9 +134,9 @@ class EmailDataSourceImpl extends EmailDataSource { } @override - Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds) { + Future> deleteMultipleEmailsPermanently(AccountId accountId, List emailIds) { return Future.sync(() async { - return await emailAPI.deleteMultipleEmailsPermanently(session, accountId, emailIds); + return await emailAPI.deleteMultipleEmailsPermanently(accountId, emailIds); }).catchError((error) { throw error; }); diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index df168479d..43700eef0 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -7,13 +7,10 @@ import 'package:external_path/external_path.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; -import 'package:jmap_dart_client/jmap/core/capability/core_capability.dart'; import 'package:jmap_dart_client/jmap/core/patch_object.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/reference_id.dart'; import 'package:jmap_dart_client/jmap/core/reference_prefix.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/jmap_request.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_method.dart'; @@ -31,7 +28,6 @@ import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart' import 'package:model/model.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_to_trash_request.dart'; -import 'package:tmail_ui_user/main/error/capability_validator.dart'; class EmailAPI { @@ -386,54 +382,34 @@ class EmailAPI { }); } - Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds) async { - requireCapability(session, [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]); + Future> deleteMultipleEmailsPermanently(AccountId accountId, List emailIds) async { + final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final setEmailMethod = SetEmailMethod(accountId) + ..addDestroy(emailIds.map((emailId) => emailId.id).toSet()); - final coreCapability = (session.capabilities[CapabilityIdentifier.jmapCore] as CoreCapability); - final maxMethodCount = coreCapability.maxCallsInRequest.value.toInt(); + final setEmailInvocation = requestBuilder.invocation(setEmailMethod); - List listEmailResult = []; - var start = 0; - var end = 0; - while (end < emailIds.length) { - start = end; - if (emailIds.length - start >= maxMethodCount) { - end = maxMethodCount; - } else { - end = emailIds.length; - } - log('EmailAPI::deleteMultipleEmailPermanently(): delete from $start to $end / ${emailIds.length}'); - final currentExecuteList = emailIds.sublist(start, end); + final response = await (requestBuilder + ..usings(setEmailMethod.requiredCapabilities)) + .build() + .execute(); - final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); - final currentSetEmailInvocations = currentExecuteList - .map((emailId) { - return SetEmailMethod(accountId) - ..addDestroy(Set.of([emailId.id])); - }) - .map(requestBuilder.invocation) - .toList(); + final setEmailResponse = response.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize); - final response = await (requestBuilder - ..usings(Set.of([CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]))) - .build() - .execute(); + final listIdResult = setEmailResponse?.destroyed; - currentSetEmailInvocations - .map((currentInvocation) => response.parse(currentInvocation.methodCallId, SetEmailResponse.deserialize)) - .map((response) => response?.destroyed?.map((id) => EmailId(id))) - .where((listId) => listId != null && listId.isNotEmpty) - .forEach((listId) { - listEmailResult.addAll(listId!); - }); + if (listIdResult != null) { + return listIdResult.map((id) => EmailId(id)).toList(); } - return listEmailResult; + return List.empty(); } Future deleteEmailPermanently(AccountId accountId, EmailId emailId) async { final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); - final setEmailMethod = SetEmailMethod(accountId) + final setEmailMethod = SetEmailMethod(accountId) ..addDestroy(Set.of([emailId.id])); final setEmailInvocation = requestBuilder.invocation(setEmailMethod); diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 52064daef..fac295e5b 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -1,7 +1,6 @@ import 'package:dio/dio.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; @@ -117,8 +116,8 @@ class EmailRepositoryImpl extends EmailRepository { } @override - Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds) { - return emailDataSource.deleteMultipleEmailsPermanently(session, accountId, emailIds); + Future> deleteMultipleEmailsPermanently(AccountId accountId, List emailIds) { + return emailDataSource.deleteMultipleEmailsPermanently(accountId, emailIds); } @override diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index 7148c495e..08ef59b6e 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; @@ -59,7 +58,7 @@ abstract class EmailRepository { Future updateEmailDrafts(AccountId accountId, Email newEmail, EmailId oldEmailId); - Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds); + Future> deleteMultipleEmailsPermanently(AccountId accountId, List emailIds); Future deleteEmailPermanently(AccountId accountId, EmailId emailId); } \ No newline at end of file diff --git a/lib/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart b/lib/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart index 968e2f832..1d3e6a7de 100644 --- a/lib/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart +++ b/lib/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart @@ -1,7 +1,6 @@ import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; @@ -11,9 +10,9 @@ class DeleteMultipleEmailsPermanentlyInteractor { DeleteMultipleEmailsPermanentlyInteractor(this.emailRepository); - Stream> execute(Session session, AccountId accountId, List emailIds) async* { + Stream> execute(AccountId accountId, List emailIds) async* { try { - final listResult = await emailRepository.deleteMultipleEmailsPermanently(session, accountId, emailIds); + final listResult = await emailRepository.deleteMultipleEmailsPermanently(accountId, emailIds); if (listResult.length == emailIds.length) { yield Right(DeleteMultipleEmailsPermanentlyAllSuccess(listResult)); } else if (listResult.isNotEmpty) { diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 36dad9217..55de15634 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -50,6 +50,7 @@ import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_ma import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_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/move_multiple_email_to_trash_state.dart'; @@ -113,36 +114,49 @@ class MailboxController extends BaseMailboxController { }); mailboxDashBoardController.viewState.listen((state) { - state.map((success) { - log('MailboxController::onReady(): ${success.runtimeType}'); + state.fold( + (failure) { + log('MailboxController::onReady(): ${failure.runtimeType}'); - if (success is MarkAsMultipleEmailReadAllSuccess - || success is MarkAsMultipleEmailReadHasSomeEmailFailure) { - mailboxDashBoardController.clearState(); - refreshMailboxChanges(); - } else if (success is MoveMultipleEmailToMailboxAllSuccess - || success is MoveMultipleEmailToMailboxHasSomeEmailFailure) { - mailboxDashBoardController.clearState(); - refreshMailboxChanges(); - } else if (success is MoveMultipleEmailToTrashAllSuccess - || success is MoveMultipleEmailToTrashHasSomeEmailFailure) { - mailboxDashBoardController.clearState(); - refreshMailboxChanges(); - } else if (success is DeleteMultipleEmailsPermanentlyAllSuccess - || success is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure) { - mailboxDashBoardController.clearState(); - refreshMailboxChanges(); - } else if (success is MarkAsEmailReadSuccess - || success is MoveToMailboxSuccess - || success is MoveToTrashSuccess - || success is DeleteEmailPermanentlySuccess - || success is SaveEmailAsDraftsSuccess - || success is RemoveEmailDraftsSuccess - || success is SendEmailSuccess - || success is UpdateEmailDraftsSuccess) { - refreshMailboxChanges(); + if (failure is EmptyTrashFolderFailure) { + mailboxDashBoardController.clearState(); + refreshMailboxChanges(); + } + }, + (success) { + log('MailboxController::onReady(): ${success.runtimeType}'); + + if (success is MarkAsMultipleEmailReadAllSuccess + || success is MarkAsMultipleEmailReadHasSomeEmailFailure) { + mailboxDashBoardController.clearState(); + refreshMailboxChanges(); + } else if (success is MoveMultipleEmailToMailboxAllSuccess + || success is MoveMultipleEmailToMailboxHasSomeEmailFailure) { + mailboxDashBoardController.clearState(); + refreshMailboxChanges(); + } else if (success is MoveMultipleEmailToTrashAllSuccess + || success is MoveMultipleEmailToTrashHasSomeEmailFailure) { + mailboxDashBoardController.clearState(); + refreshMailboxChanges(); + } else if (success is DeleteMultipleEmailsPermanentlyAllSuccess + || success is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure) { + mailboxDashBoardController.clearState(); + refreshMailboxChanges(); + } else if (success is EmptyTrashFolderSuccess) { + mailboxDashBoardController.clearState(); + refreshMailboxChanges(); + } else if (success is MarkAsEmailReadSuccess + || success is MoveToMailboxSuccess + || success is MoveToTrashSuccess + || success is DeleteEmailPermanentlySuccess + || success is SaveEmailAsDraftsSuccess + || success is RemoveEmailDraftsSuccess + || success is SendEmailSuccess + || success is UpdateEmailDraftsSuccess) { + refreshMailboxChanges(); + } } - }); + ); }); _openMailboxEventController.stream.throttleTime(Duration(milliseconds: 800)).listen((event) { @@ -227,6 +241,7 @@ class MailboxController extends BaseMailboxController { } void refreshMailboxChanges() { + log('MailboxController::refreshMailboxChanges():'); final accountId = mailboxDashBoardController.accountId.value; if (accountId != null && currentMailboxState != null) { consumeState(_refreshAllMailboxInteractor.execute(accountId, currentMailboxState!)); diff --git a/lib/features/thread/data/local/email_cache_manager.dart b/lib/features/thread/data/local/email_cache_manager.dart index 98595f16d..75ad57d30 100644 --- a/lib/features/thread/data/local/email_cache_manager.dart +++ b/lib/features/thread/data/local/email_cache_manager.dart @@ -1,4 +1,5 @@ +import 'package:core/core.dart'; import 'package:model/model.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; @@ -38,6 +39,7 @@ class EmailCacheManager { Future update({List? updated, List? created, List? destroyed}) async { final emailCacheExist = await _emailCacheClient.isExistTable(); + log('EmailCacheManager::update(): emailCacheExist: $emailCacheExist'); if (emailCacheExist) { final updatedCacheEmails = updated ?.map((email) => email.toEmailCache()).toList() ?? []; @@ -45,6 +47,9 @@ class EmailCacheManager { ?.map((email) => email.toEmailCache()).toList() ?? []; final destroyedCacheEmails = destroyed ?.map((emailId) => emailId.id.value).toList() ?? []; + + log('EmailCacheManager::update(): destroyedCacheEmails: ${destroyedCacheEmails.length}'); + await Future.wait([ _emailCacheClient.updateMultipleItem(updatedCacheEmails.toMap()), _emailCacheClient.insertMultipleItem(createdCacheEmails.toMap()), diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 83fbcc7ac..18c666d48 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -7,8 +7,12 @@ import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/state_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; @@ -22,8 +26,9 @@ class ThreadRepositoryImpl extends ThreadRepository { final Map mapDataSource; final StateDataSource stateDataSource; + final EmailDataSource emailDataSource; - ThreadRepositoryImpl(this.mapDataSource, this.stateDataSource); + ThreadRepositoryImpl(this.mapDataSource, this.stateDataSource, this.emailDataSource); @override Stream getAllEmail( @@ -44,6 +49,7 @@ class ThreadRepositoryImpl extends ThreadRepository { filterOption: emailFilter?.filterOption), stateDataSource.getState(StateType.email) ]).then((List response) { + log('ThreadRepositoryImpl::getAllEmail(): localEmail: ${response.first}'); return EmailsResponse(emailList: response.first, state: response.last); }); @@ -319,4 +325,52 @@ class ThreadRepositoryImpl extends ThreadRepository { yield newEmailResponse; } + + @override + Future emptyTrashFolder(AccountId accountId, MailboxId trashMailboxId) async { + var finalResult = true; + var hasEmails = true; + + while (hasEmails) { + Email? lastEmail; + + final emailsResponse = await mapDataSource[DataSourceType.network]!.getAllEmail( + accountId, + sort: Set() + ..add(EmailComparator(EmailComparatorProperty.receivedAt) + ..setIsAscending(false)), + filter: EmailFilterCondition(inMailbox: trashMailboxId, before: lastEmail?.receivedAt), + properties: Properties({EmailProperty.id})); + + if (emailsResponse.state != null) { + await _updateState(emailsResponse.state!); + } + + var newEmailList = emailsResponse.emailList ?? []; + if (lastEmail != null) { + newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList(); + } + + log('ThreadRepositoryImpl::emptyTrashFolder(): ${newEmailList.length}'); + + if (newEmailList.isNotEmpty == true) { + lastEmail = newEmailList.last; + hasEmails = true; + final emailIds = newEmailList.map((email) => email.id).toList(); + + final listEmailIdDeleted = await emailDataSource.deleteMultipleEmailsPermanently(accountId, emailIds); + + if (listEmailIdDeleted.isNotEmpty && listEmailIdDeleted.length == emailIds.length) { + await _updateEmailCache(newDestroyed: listEmailIdDeleted); + finalResult = true; + } else { + finalResult = false; + } + } else { + hasEmails = false; + } + } + + return finalResult; + } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index f46e8224e..228de215b 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -64,4 +64,6 @@ abstract class ThreadRepository { Properties? properties, } ); + + Future emptyTrashFolder(AccountId accountId, MailboxId trashMailboxId); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/empty_trash_folder_state.dart b/lib/features/thread/domain/state/empty_trash_folder_state.dart new file mode 100644 index 000000000..b703598e4 --- /dev/null +++ b/lib/features/thread/domain/state/empty_trash_folder_state.dart @@ -0,0 +1,18 @@ +import 'package:core/core.dart'; + +class EmptyTrashFolderSuccess extends UIState { + + EmptyTrashFolderSuccess(); + + @override + List get props => []; +} + +class EmptyTrashFolderFailure extends FeatureFailure { + final exception; + + EmptyTrashFolderFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/empty_trash_folder_interactor.dart b/lib/features/thread/domain/usecases/empty_trash_folder_interactor.dart new file mode 100644 index 000000000..c300047ea --- /dev/null +++ b/lib/features/thread/domain/usecases/empty_trash_folder_interactor.dart @@ -0,0 +1,27 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; + +class EmptyTrashFolderInteractor { + final ThreadRepository threadRepository; + + EmptyTrashFolderInteractor(this.threadRepository); + + Stream> execute(AccountId accountId, MailboxId trashMailboxId) async* { + try { + yield Right(LoadingState()); + + final result = await threadRepository.emptyTrashFolder(accountId, trashMailboxId); + if (result) { + yield Right(EmptyTrashFolderSuccess()); + } else { + yield Left(EmptyTrashFolderFailure(null)); + } + } catch (e) { + yield Left(EmptyTrashFolderFailure(e)); + } + } +} \ 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 7e084d863..cf97f158e 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -23,6 +23,7 @@ import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dar import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/data/repository/thread_repository_impl.dart'; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.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'; @@ -57,6 +58,7 @@ class ThreadBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); } @@ -96,6 +98,7 @@ class ThreadBindings extends BaseBindings { Get.lazyPut(() => SearchMoreEmailInteractor(Get.find())); Get.lazyPut(() => MoveMultipleEmailToTrashInteractor(Get.find())); Get.lazyPut(() => DeleteMultipleEmailsPermanentlyInteractor(Get.find())); + Get.lazyPut(() => EmptyTrashFolderInteractor(Get.find())); } @override @@ -111,7 +114,8 @@ class ThreadBindings extends BaseBindings { DataSourceType.network: Get.find(), DataSourceType.local: Get.find() }, - Get.find() + Get.find(), + Get.find(), )); Get.lazyPut(() => EmailRepositoryImpl( Get.find(), diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 7dd7eee1f..40d4fef3f 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -38,6 +38,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_da import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_action.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/empty_trash_folder_state.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'; @@ -47,6 +48,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_t import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_all_email_state.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/domain/usecases/empty_trash_folder_interactor.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'; @@ -83,6 +85,7 @@ class ThreadController extends BaseController { final SearchMoreEmailInteractor _searchMoreEmailInteractor; final MoveMultipleEmailToTrashInteractor _moveMultipleEmailToTrashInteractor; final DeleteMultipleEmailsPermanentlyInteractor _deleteMultipleEmailsPermanentlyInteractor; + final EmptyTrashFolderInteractor _emptyTrashFolderInteractor; final emailList = [].obs; final emailListSearch = [].obs; @@ -118,6 +121,7 @@ class ThreadController extends BaseController { this._searchMoreEmailInteractor, this._moveMultipleEmailToTrashInteractor, this._deleteMultipleEmailsPermanentlyInteractor, + this._emptyTrashFolderInteractor, ); @override @@ -214,6 +218,8 @@ class ThreadController extends BaseController { } else if (failure is MarkAsStarMultipleEmailAllFailure || failure is MarkAsStarMultipleEmailFailure) { _markAsStarMultipleEmailFailure(failure); + } else if (failure is EmptyTrashFolderFailure) { + _emptyTrashFolderFailure(failure); } }, (success) { @@ -234,6 +240,8 @@ class ThreadController extends BaseController { } else if (success is DeleteMultipleEmailsPermanentlyAllSuccess || success is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure) { _deleteMultipleEmailsPermanentlySuccess(success); + } else if (success is EmptyTrashFolderSuccess) { + _emptyTrashFolderSuccess(success); } } ); @@ -272,6 +280,10 @@ class ThreadController extends BaseController { final emailsAfterChanges = success.emailList; final newListEmail = emailsAfterChanges.combine(emailsBeforeChanges); emailList.value = newListEmail; + + if (emailList.isEmpty) { + refreshAllEmail(); + } } void _getAllEmailAction(AccountId accountId, {MailboxId? mailboxId}) { @@ -856,6 +868,7 @@ class ThreadController extends BaseController { switch(actionType) { case DeleteActionType.all: + _emptyTrashFolderAction(); break; case DeleteActionType.multiple: _deleteMultipleEmailsPermanently(listEmailSelected); @@ -868,10 +881,9 @@ class ThreadController extends BaseController { void _deleteMultipleEmailsPermanently(List emailList) { cancelSelectEmail(); - final session = mailboxDashBoardController.sessionCurrent; final listEmailIds = emailList.map((email) => email.id).toList(); - if (session != null && _accountId != null && listEmailIds.isNotEmpty) { - consumeState(_deleteMultipleEmailsPermanentlyInteractor.execute(session, _accountId!, listEmailIds)); + if ( _accountId != null && listEmailIds.isNotEmpty) { + consumeState(_deleteMultipleEmailsPermanentlyInteractor.execute(_accountId!, listEmailIds)); } } @@ -896,6 +908,35 @@ class ThreadController extends BaseController { _refreshEmailChanges(); } + void _emptyTrashFolderAction() { + cancelSelectEmail(); + + final trashMailboxId = mailboxDashBoardController.mapDefaultMailboxId[PresentationMailbox.roleTrash]; + log('ThreadController::_emptyTrashFolderAction(): trashMailboxId: $trashMailboxId'); + if (_accountId != null && trashMailboxId != null) { + consumeState(_emptyTrashFolderInteractor.execute(_accountId!, trashMailboxId)); + } + } + + void _emptyTrashFolderSuccess(EmptyTrashFolderSuccess success) { + mailboxDashBoardController.dispatchState(Right(success)); + + if (currentContext != null && currentOverlayContext != null) { + _appToast.showToastWithIcon( + currentOverlayContext!, + widthToast: _responsiveUtils.isDesktop(currentContext!) ? 360 : null, + message: AppLocalizations.of(currentContext!).toast_message_empty_trash_folder_success, + icon: _imagePaths.icDeleteToast); + } + + refreshAllEmail(); + } + + void _emptyTrashFolderFailure(EmptyTrashFolderFailure failure) { + mailboxDashBoardController.dispatchState(Left(failure)); + refreshAllEmail(); + } + void openMailboxLeftMenu() { mailboxDashBoardController.openDrawer(); } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 18bbb3c7b..dfa179368 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -43,6 +43,9 @@ class ThreadView extends GetWidget with UserSettingPopupMenuMi child: _buildHeader(context)), _buildSearchInputFormForMobile(context), _buildSearchButtonViewForMobile(context), + Obx(() => controller.isMailboxTrash && controller.emailList.isNotEmpty && !controller.isSearchActive() + ? _buildEmptyTrashButton(context) + : SizedBox.shrink()), Expanded(child: Container( color: AppColor.colorBgDesktop, margin: _responsiveUtils.isDesktop(context) ? EdgeInsets.all(16) : EdgeInsets.zero, @@ -472,11 +475,11 @@ class ThreadView extends GetWidget with UserSettingPopupMenuMi (success) { if (controller.isSearchActive()) { return success is SearchingState - ? Padding(padding: EdgeInsets.only(top: 16), child: _loadingWidget) + ? Padding(padding: EdgeInsets.symmetric(vertical: 16), child: _loadingWidget) : SizedBox.shrink(); } else { return success is LoadingState - ? Padding(padding: EdgeInsets.only(top: 16), child: _loadingWidget) + ? Padding(padding: EdgeInsets.symmetric(vertical: 16), child: _loadingWidget) : SizedBox.shrink(); } })); @@ -630,7 +633,7 @@ class ThreadView extends GetWidget with UserSettingPopupMenuMi return controller.emailListSearch.length > 0 ? Container( width: double.infinity, - margin: EdgeInsets.zero, + margin: EdgeInsets.only(bottom: 16), padding: EdgeInsets.symmetric(vertical: 12, horizontal: 24), decoration: BoxDecoration( borderRadius: _responsiveUtils.isDesktop(context) @@ -647,4 +650,41 @@ class ThreadView extends GetWidget with UserSettingPopupMenuMi } }); } + + Widget _buildEmptyTrashButton(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(14)), + border: Border.all(color: AppColor.colorLineLeftEmailView), + color: Colors.white), + margin: EdgeInsets.only(left: 16, right: 16, + bottom: _responsiveUtils.isDesktop(context) ? 0 : 16, + top: _responsiveUtils.isDesktop(context) ? 16 : 0), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row(children: [ + Padding( + padding: EdgeInsets.only(right: 16), + child: SvgPicture.asset(_imagePaths.icDeleteTrash, fit: BoxFit.fill)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 8), + child: Text( + AppLocalizations.of(context).message_delete_all_email_in_trash_button, + style: TextStyle(color: AppColor.colorContentEmail, fontSize: 13, fontWeight: FontWeight.w500))), + TextButton( + onPressed: () => controller.deleteEmailsPermanently(context, DeleteActionType.all), + child: Text( + AppLocalizations.of(context).empty_trash_now, + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton) + ) + ) + ] + ) + ) + ]), + ); + } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 3aa4bbe73..9a8692500 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-03-31T18:18:59.491193", + "@@last_modified": "2022-04-05T16:53:32.520037", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -923,5 +923,91 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "message_delete_all_email_in_trash_button": "All messages in Trash will be deleted if you reach limited storage.", + "@message_delete_all_email_in_trash_button": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "empty_trash_now": "Empty trash now", + "@empty_trash_now": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "empty_trash_folder": "Empty trash folder", + "@empty_trash_folder": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "empty_trash_dialog_message": "You are about to permanently delete all items in Trash . Do you want to continue?", + "@empty_trash_dialog_message": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "delete_all": "Delete all", + "@delete_all": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toast_message_delete_multiple_email_permanently_success": "{count} Messages has been deleted forever", + "@toast_message_delete_multiple_email_permanently_success": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } + }, + "toast_message_delete_a_email_permanently_success": "Message has been deleted forever", + "@toast_message_delete_a_email_permanently_success": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "delete_permanently": "Delete permanently", + "@delete_permanently": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "delete_messages_forever": "Delete messages forever", + "@delete_messages_forever": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "delete_message_forever": "Delete message forever", + "@delete_message_forever": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "delete_multiple_messages_dialog": "You are about to permanently delete {count} items in Trash . Do you want to continue?", + "@delete_multiple_messages_dialog": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } + }, + "delete_single_message_dialog": "You are about to permanently delete this message. Do you want to continue?", + "@delete_single_message_dialog": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toast_message_empty_trash_folder_success": "All messages has been deleted forever", + "@toast_message_empty_trash_folder_success": { + "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 e0b90f155..34af109dc 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1017,7 +1017,8 @@ class AppLocalizations { String toast_message_delete_multiple_email_permanently_success(int count) { return Intl.message( '$count Messages has been deleted forever', - name: 'toast_message_delete_multiple_email_permanently_success'); + name: 'toast_message_delete_multiple_email_permanently_success', + args: [count]); } String get toast_message_delete_a_email_permanently_success { @@ -1047,7 +1048,8 @@ class AppLocalizations { String delete_multiple_messages_dialog(int count) { return Intl.message( 'You are about to permanently delete $count items in Trash . Do you want to continue?', - name: 'delete_multiple_messages_dialog'); + name: 'delete_multiple_messages_dialog', + args: [count]); } String get delete_single_message_dialog { @@ -1055,4 +1057,10 @@ class AppLocalizations { 'You are about to permanently delete this message. Do you want to continue?', name: 'delete_single_message_dialog'); } + + String get toast_message_empty_trash_folder_success { + return Intl.message( + 'All messages has been deleted forever', + name: 'toast_message_empty_trash_folder_success'); + } } \ No newline at end of file