TF-1710 Add empty spam folder interactor

(cherry picked from commit f0d22cd8925185607b5dea74f2cf40052980d19c)
This commit is contained in:
dab246
2023-08-03 16:01:29 +07:00
committed by Dat Vu
parent 18c007d0b2
commit b83eb87675
12 changed files with 158 additions and 48 deletions
@@ -50,7 +50,7 @@ abstract class ThreadDataSource {
Future<void> update(AccountId accountId, UserName userName, {List<Email>? updated, List<Email>? created, List<EmailId>? destroyed}); Future<void> update(AccountId accountId, UserName userName, {List<Email>? updated, List<Email>? created, List<EmailId>? destroyed});
Future<List<EmailId>> emptyTrashFolder( Future<List<EmailId>> emptyMailboxFolder(
Session session, Session session,
AccountId accountId, AccountId accountId,
MailboxId mailboxId, MailboxId mailboxId,
@@ -91,7 +91,7 @@ class LocalThreadDataSourceImpl extends ThreadDataSource {
} }
@override @override
Future<List<EmailId>> emptyTrashFolder( Future<List<EmailId>> emptyMailboxFolder(
Session session, Session session,
AccountId accountId, AccountId accountId,
MailboxId mailboxId, MailboxId mailboxId,
@@ -85,14 +85,14 @@ class ThreadDataSourceImpl extends ThreadDataSource {
} }
@override @override
Future<List<EmailId>> emptyTrashFolder( Future<List<EmailId>> emptyMailboxFolder(
Session session, Session session,
AccountId accountId, AccountId accountId,
MailboxId mailboxId, MailboxId mailboxId,
Future<void> Function(List<EmailId>? newDestroyed) updateDestroyedEmailCache Future<void> Function(List<EmailId>? newDestroyed) updateDestroyedEmailCache
) { ) {
return Future.sync(() async { return Future.sync(() async {
return await _threadIsolateWorker.emptyTrashFolder( return await _threadIsolateWorker.emptyMailboxFolder(
session, session,
accountId, accountId,
mailboxId, mailboxId,
@@ -5,19 +5,19 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
class EmptyTrashFolderArguments with EquatableMixin { class EmptyMailboxFolderArguments with EquatableMixin {
final Session session; final Session session;
final AccountId accountId; final AccountId accountId;
final MailboxId trashMailboxId; final MailboxId mailboxId;
final ThreadAPI threadAPI; final ThreadAPI threadAPI;
final EmailAPI emailAPI; final EmailAPI emailAPI;
EmptyTrashFolderArguments( EmptyMailboxFolderArguments(
this.session, this.session,
this.threadAPI, this.threadAPI,
this.emailAPI, this.emailAPI,
this.accountId, this.accountId,
this.trashMailboxId, this.mailboxId,
); );
@override @override
@@ -26,6 +26,6 @@ class EmptyTrashFolderArguments with EquatableMixin {
accountId, accountId,
emailAPI, emailAPI,
threadAPI, threadAPI,
trashMailboxId mailboxId
]; ];
} }
@@ -14,8 +14,9 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/email_property.dart'; import 'package:model/email/email_property.dart';
import 'package:model/extensions/list_email_extension.dart'; import 'package:model/extensions/list_email_extension.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/thread/data/model/empty_trash_folder_arguments.dart'; import 'package:tmail_ui_user/features/thread/data/model/empty_mailbox_folder_arguments.dart';
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
import 'package:tmail_ui_user/features/thread/domain/exceptions/thread_exceptions.dart';
import 'package:worker_manager/worker_manager.dart'; import 'package:worker_manager/worker_manager.dart';
class ThreadIsolateWorker { class ThreadIsolateWorker {
@@ -25,29 +26,35 @@ class ThreadIsolateWorker {
ThreadIsolateWorker(this._threadAPI, this._emailAPI, this._isolateExecutor); ThreadIsolateWorker(this._threadAPI, this._emailAPI, this._isolateExecutor);
Future<List<EmailId>> emptyTrashFolder( Future<List<EmailId>> emptyMailboxFolder(
Session session, Session session,
AccountId accountId, AccountId accountId,
MailboxId mailboxId, MailboxId mailboxId,
Future<void> Function(List<EmailId>? newDestroyed) updateDestroyedEmailCache, Future<void> Function(List<EmailId>? newDestroyed) updateDestroyedEmailCache,
) async { ) async {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
return _emptyTrashFolderOnWeb(session, accountId, mailboxId, updateDestroyedEmailCache); return _emptyMailboxFolderOnWeb(session, accountId, mailboxId, updateDestroyedEmailCache);
} else { } else {
final result = await _isolateExecutor.execute( final result = await _isolateExecutor.execute(
arg1: EmptyTrashFolderArguments(session, _threadAPI, _emailAPI, accountId, mailboxId), arg1: EmptyMailboxFolderArguments(session, _threadAPI, _emailAPI, accountId, mailboxId),
fun1: _emptyTrashFolderAction, fun1: _emptyMailboxFolderAction,
notification: (value) { notification: (value) {
if (value is List<EmailId>) { if (value is List<EmailId>) {
updateDestroyedEmailCache.call(value); updateDestroyedEmailCache.call(value);
log('ThreadIsolateWorker::emptyTrashFolder(): onUpdateProgress: PERCENT ${value.length}'); log('ThreadIsolateWorker::emptyMailboxFolder(): onUpdateProgress: PERCENT ${value.length}');
} }
}); }
return result; );
if (result.isEmpty) {
throw NotFoundEmailsDeletedException();
} else {
return result;
}
} }
} }
static Future<List<EmailId>> _emptyTrashFolderAction(EmptyTrashFolderArguments args, TypeSendPort sendPort) async { static Future<List<EmailId>> _emptyMailboxFolderAction(EmptyMailboxFolderArguments args, TypeSendPort sendPort) async {
List<EmailId> emailListCompleted = List.empty(growable: true); List<EmailId> emailListCompleted = List.empty(growable: true);
try { try {
var hasEmails = true; var hasEmails = true;
@@ -60,7 +67,7 @@ class ThreadIsolateWorker {
sort: <Comparator>{}..add( sort: <Comparator>{}..add(
EmailComparator(EmailComparatorProperty.receivedAt) EmailComparator(EmailComparatorProperty.receivedAt)
..setIsAscending(false)), ..setIsAscending(false)),
filter: EmailFilterCondition(inMailbox: args.trashMailboxId, before: lastEmail?.receivedAt), filter: EmailFilterCondition(inMailbox: args.mailboxId, before: lastEmail?.receivedAt),
properties: Properties({EmailProperty.id})); properties: Properties({EmailProperty.id}));
var newEmailList = emailsResponse.emailList ?? <Email>[]; var newEmailList = emailsResponse.emailList ?? <Email>[];
@@ -68,7 +75,7 @@ class ThreadIsolateWorker {
newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList(); newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList();
} }
log('ThreadIsolateWorker::_emptyTrashFolderAction(): ${newEmailList.length}'); log('ThreadIsolateWorker::_emptyMailboxFolderAction(): ${newEmailList.length}');
if (newEmailList.isNotEmpty) { if (newEmailList.isNotEmpty) {
lastEmail = newEmailList.last; lastEmail = newEmailList.last;
@@ -86,16 +93,16 @@ class ThreadIsolateWorker {
} }
} }
} catch (e) { } catch (e) {
log('ThreadIsolateWorker::_emptyTrashFolderAction(): ERROR: $e'); log('ThreadIsolateWorker::_emptyMailboxFolderAction(): ERROR: $e');
} }
log('ThreadIsolateWorker::_emptyTrashFolderAction(): TOTAL_REMOVE: ${emailListCompleted.length}'); log('ThreadIsolateWorker::_emptyMailboxFolderAction(): TOTAL_REMOVE: ${emailListCompleted.length}');
return emailListCompleted; return emailListCompleted;
} }
Future<List<EmailId>> _emptyTrashFolderOnWeb( Future<List<EmailId>> _emptyMailboxFolderOnWeb(
Session session, Session session,
AccountId accountId, AccountId accountId,
MailboxId trashMailboxId, MailboxId mailboxId,
Future<void> Function(List<EmailId> newDestroyed) updateDestroyedEmailCache, Future<void> Function(List<EmailId> newDestroyed) updateDestroyedEmailCache,
) async { ) async {
List<EmailId> emailListCompleted = List.empty(growable: true); List<EmailId> emailListCompleted = List.empty(growable: true);
@@ -110,7 +117,7 @@ class ThreadIsolateWorker {
sort: <Comparator>{}..add( sort: <Comparator>{}..add(
EmailComparator(EmailComparatorProperty.receivedAt) EmailComparator(EmailComparatorProperty.receivedAt)
..setIsAscending(false)), ..setIsAscending(false)),
filter: EmailFilterCondition(inMailbox: trashMailboxId, before: lastEmail?.receivedAt), filter: EmailFilterCondition(inMailbox: mailboxId, before: lastEmail?.receivedAt),
properties: Properties({EmailProperty.id})); properties: Properties({EmailProperty.id}));
var newEmailList = emailsResponse.emailList ?? <Email>[]; var newEmailList = emailsResponse.emailList ?? <Email>[];
@@ -118,7 +125,7 @@ class ThreadIsolateWorker {
newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList(); newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList();
} }
log('ThreadIsolateWorker::_emptyTrashFolderOnWeb(): ${newEmailList.length}'); log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ${newEmailList.length}');
if (newEmailList.isNotEmpty) { if (newEmailList.isNotEmpty) {
lastEmail = newEmailList.last; lastEmail = newEmailList.last;
@@ -135,9 +142,9 @@ class ThreadIsolateWorker {
} }
} }
} catch (e) { } catch (e) {
log('ThreadIsolateWorker::_emptyTrashFolderOnWeb(): ERROR: $e'); log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ERROR: $e');
} }
log('ThreadIsolateWorker::_emptyTrashFolderOnWeb(): TOTAL_REMOVE: ${emailListCompleted.length}'); log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): TOTAL_REMOVE: ${emailListCompleted.length}');
return emailListCompleted; return emailListCompleted;
} }
} }
@@ -1,5 +1,6 @@
import 'package:core/core.dart'; import 'package:core/data/model/source_type/data_source_type.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart' as dartz; import 'package:dartz/dartz.dart' as dartz;
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
@@ -300,7 +301,7 @@ class ThreadRepositoryImpl extends ThreadRepository {
@override @override
Future<List<EmailId>> emptyTrashFolder(Session session, AccountId accountId, MailboxId trashMailboxId) async { Future<List<EmailId>> emptyTrashFolder(Session session, AccountId accountId, MailboxId trashMailboxId) async {
return mapDataSource[DataSourceType.network]!.emptyTrashFolder( return mapDataSource[DataSourceType.network]!.emptyMailboxFolder(
session, session,
accountId, accountId,
trashMailboxId, trashMailboxId,
@@ -377,4 +378,20 @@ class ThreadRepositoryImpl extends ThreadRepository {
) { ) {
return mapDataSource[DataSourceType.network]!.getEmailById(session, accountId, emailId, properties: properties); return mapDataSource[DataSourceType.network]!.getEmailById(session, accountId, emailId, properties: properties);
} }
@override
Future<List<EmailId>> emptySpamFolder(Session session, AccountId accountId, MailboxId spamMailboxId) {
return mapDataSource[DataSourceType.network]!.emptyMailboxFolder(
session,
accountId,
spamMailboxId,
(listEmailIdDeleted) async {
await _updateEmailCache(
accountId,
session.username,
newDestroyed: listEmailIdDeleted
);
},
);
}
} }
@@ -0,0 +1,2 @@
class NotFoundEmailsDeletedException implements Exception {}
@@ -64,4 +64,10 @@ abstract class ThreadRepository {
EmailId emailId, EmailId emailId,
{Properties? properties} {Properties? properties}
); );
Future<List<EmailId>> emptySpamFolder(
Session session,
AccountId accountId,
MailboxId spamMailboxId,
);
} }
@@ -0,0 +1,26 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/base/state/ui_action_state.dart';
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
class EmptySpamFolderLoading extends LoadingState {}
class EmptySpamFolderSuccess extends UIActionState {
final List<EmailId> emailIds;
EmptySpamFolderSuccess(
this.emailIds, {
jmap.State? currentEmailState,
jmap.State? currentMailboxState,
}) : super(currentEmailState, currentMailboxState);
@override
List<Object?> get props => [emailIds, ...super.props];
}
class EmptySpamFolderFailure extends FeatureFailure {
EmptySpamFolderFailure(dynamic exception) : super(exception: exception);
}
@@ -1,13 +1,23 @@
import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart';
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
class EmptyTrashFolderLoading extends LoadingState {}
class EmptyTrashFolderSuccess extends UIActionState { class EmptyTrashFolderSuccess extends UIActionState {
EmptyTrashFolderSuccess({ final List<EmailId> emailIds;
jmap.State? currentEmailState,
jmap.State? currentMailboxState, EmptyTrashFolderSuccess(
}) : super(currentEmailState, currentMailboxState); this.emailIds, {
jmap.State? currentEmailState,
jmap.State? currentMailboxState,
}) : super(currentEmailState, currentMailboxState);
@override
List<Object?> get props => [emailIds, ...super.props];
} }
class EmptyTrashFolderFailure extends FeatureFailure { class EmptyTrashFolderFailure extends FeatureFailure {
@@ -0,0 +1,45 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.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/mailbox/mailbox.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart';
import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart';
import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart';
class EmptySpamFolderInteractor {
final ThreadRepository threadRepository;
final MailboxRepository _mailboxRepository;
final EmailRepository _emailRepository;
EmptySpamFolderInteractor(
this.threadRepository,
this._mailboxRepository,
this._emailRepository
);
Stream<Either<Failure, Success>> execute(Session session, AccountId accountId, MailboxId spamMailboxId) async* {
try {
yield Right<Failure, Success>(EmptySpamFolderLoading());
final listState = await Future.wait([
_mailboxRepository.getMailboxState(session, accountId),
_emailRepository.getEmailState(session, accountId),
], eagerError: true);
final currentMailboxState = listState.first;
final currentEmailState = listState.last;
final emailIdDeleted = await threadRepository.emptySpamFolder(session, accountId, spamMailboxId);
yield Right<Failure, Success>(EmptySpamFolderSuccess(
emailIdDeleted,
currentMailboxState: currentMailboxState,
currentEmailState: currentEmailState,
));
} catch (e) {
yield Left<Failure, Success>(EmptySpamFolderFailure(e));
}
}
}
@@ -21,7 +21,7 @@ class EmptyTrashFolderInteractor {
Stream<Either<Failure, Success>> execute(Session session, AccountId accountId, MailboxId trashMailboxId) async* { Stream<Either<Failure, Success>> execute(Session session, AccountId accountId, MailboxId trashMailboxId) async* {
try { try {
yield Right<Failure, Success>(LoadingState()); yield Right<Failure, Success>(EmptyTrashFolderLoading());
final listState = await Future.wait([ final listState = await Future.wait([
_mailboxRepository.getMailboxState(session, accountId), _mailboxRepository.getMailboxState(session, accountId),
@@ -31,15 +31,12 @@ class EmptyTrashFolderInteractor {
final currentMailboxState = listState.first; final currentMailboxState = listState.first;
final currentEmailState = listState.last; final currentEmailState = listState.last;
final result = await threadRepository.emptyTrashFolder(session, accountId, trashMailboxId); final emailIdDeleted = await threadRepository.emptyTrashFolder(session, accountId, trashMailboxId);
if (result.isNotEmpty) { yield Right<Failure, Success>(EmptyTrashFolderSuccess(
yield Right<Failure, Success>(EmptyTrashFolderSuccess( emailIdDeleted,
currentMailboxState: currentMailboxState, currentMailboxState: currentMailboxState,
currentEmailState: currentEmailState, currentEmailState: currentEmailState,
)); ));
} else {
yield Left<Failure, Success>(EmptyTrashFolderFailure(null));
}
} catch (e) { } catch (e) {
yield Left<Failure, Success>(EmptyTrashFolderFailure(e)); yield Left<Failure, Success>(EmptyTrashFolderFailure(e));
} }