diff --git a/lib/features/mailbox/data/model/mailbox_mark_as_read_arguments.dart b/lib/features/mailbox/data/model/mailbox_mark_as_read_arguments.dart new file mode 100644 index 000000000..ade081aaf --- /dev/null +++ b/lib/features/mailbox/data/model/mailbox_mark_as_read_arguments.dart @@ -0,0 +1,23 @@ + +import 'package:equatable/equatable.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/email/data/network/email_api.dart'; +import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; + +class MailboxMarkAsReadArguments with EquatableMixin { + + final AccountId accountId; + final MailboxId mailboxId; + final ThreadAPI threadAPI; + final EmailAPI emailAPI; + + MailboxMarkAsReadArguments( + this.threadAPI, + this.emailAPI, + this.accountId, + this.mailboxId); + + @override + List get props => [accountId, mailboxId]; +} \ No newline at end of file diff --git a/lib/features/mailbox/data/network/mailbox_isolate_worker.dart b/lib/features/mailbox/data/network/mailbox_isolate_worker.dart new file mode 100644 index 000000000..0ac96c0d4 --- /dev/null +++ b/lib/features/mailbox/data/network/mailbox_isolate_worker.dart @@ -0,0 +1,138 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; +import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; +import 'package:jmap_dart_client/jmap/core/sort/comparator.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/email/keyword_identifier.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/email_property.dart'; +import 'package:model/email/read_actions.dart'; +import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; +import 'package:tmail_ui_user/features/mailbox/data/model/mailbox_mark_as_read_arguments.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:worker_manager/worker_manager.dart'; + +class MailboxIsolateWorker { + + final ThreadAPI _threadApi; + final EmailAPI _emailApi; + final Executor _isolateExecutor; + + MailboxIsolateWorker(this._threadApi, this._emailApi, this._isolateExecutor); + + Future> markAsMailboxRead( + AccountId accountId, + MailboxId mailboxId, + int totalEmailUnread, + StreamController> onProgressController + ) async { + final result = await _isolateExecutor.execute( + arg1: ArgumentsSendPort(MailboxMarkAsReadArguments( + _threadApi, + _emailApi, + accountId, + mailboxId)), + fun1: (arguments) => _handleMarkAsMailboxReadAction(arguments), + onUpdateProgress: (value) { + if (value is List) { + log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: COUNT ${value.length}'); + log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: TOTAL $totalEmailUnread'); + log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: PERCENT ${value.length / totalEmailUnread}'); + log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: ${((value.length / totalEmailUnread) * 100).toInt()}%'); + onProgressController.add(Right(UpdatingMarkAsMailboxReadState( + mailboxId: mailboxId, + totalUnread: totalEmailUnread, + countRead: value.length))); + } + }); + return result; + } + + static Future> _handleMarkAsMailboxReadAction(dynamic argumentsAction) async { + if (argumentsAction is! ArgumentsSendPort) { + log('MailboxIsolateWorker::_handleMarkAsMailboxReadAction(): argumentsAction NOT ArgumentsSendPort'); + return List.empty(); + } + + final args = argumentsAction.argument as MailboxMarkAsReadArguments; + final sendPort = argumentsAction.sendPort; + + if (sendPort != null) { + log('MailboxIsolateWorker::_handleMarkAsMailboxReadAction(): SEND_PORT NOT NULL'); + } else { + log('MailboxIsolateWorker::_handleMarkAsMailboxReadAction(): SEND_PORT NULL'); + } + + List emailListCompleted = List.empty(growable: true); + + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await args.threadAPI + .getAllEmail(args.accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: args.mailboxId, + notKeyword: KeyWordIdentifier.emailSeen.value, + before: lastReceivedDate), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt) + ..setIsAscending(false)), + properties: Properties({ + EmailProperty.id, + EmailProperty.keywords, + EmailProperty.receivedAt, + })) + .then((response) { + var listEmails = response.emailList; + if (listEmails != null && listEmails.isNotEmpty && lastEmailId != null) { + listEmails = listEmails + .where((email) => email.id != lastEmailId) + .toList(); + } + return EmailsResponse(emailList: listEmails, state: response.state); + }); + final listEmailUnread = emailResponse.emailList; + + log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): listEmailUnread: ${listEmailUnread?.length}'); + + if (listEmailUnread == null || listEmailUnread.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmailUnread.last.id; + lastReceivedDate = listEmailUnread.last.receivedAt; + + final result = await args.emailAPI.markAsRead( + args.accountId, + listEmailUnread, + ReadActions.markAsRead); + + log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): MARK_READ: ${result.length}'); + emailListCompleted.addAll(result); + if (sendPort != null) { + sendPort.send(ValueUpdate(emailListCompleted)); + } + } + } + } catch (e) { + log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): ERROR: $e'); + } + log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): TOTAL_READ: ${emailListCompleted.length}'); + return emailListCompleted; + } +} diff --git a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart new file mode 100644 index 000000000..24fca8c2e --- /dev/null +++ b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart @@ -0,0 +1,63 @@ +import 'package:core/core.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; + +class MarkAsMailboxReadLoading extends UIState { + + MarkAsMailboxReadLoading(); + + @override + List get props => []; +} + +class UpdatingMarkAsMailboxReadState extends UIState { + + final MailboxId mailboxId; + final int totalUnread; + final int countRead; + + UpdatingMarkAsMailboxReadState({ + required this.mailboxId, + required this.totalUnread, + required this.countRead}); + + @override + List get props => [mailboxId, countRead, totalUnread]; +} + +class MarkAsMailboxReadAllSuccess extends UIState { + + final MailboxName mailboxName; + + MarkAsMailboxReadAllSuccess(this.mailboxName); + + @override + List get props => [mailboxName]; +} + +class MarkAsMailboxReadHasSomeEmailFailure extends UIState { + + final MailboxName mailboxName; + final int countEmailsRead; + + MarkAsMailboxReadHasSomeEmailFailure(this.mailboxName, this.countEmailsRead); + + @override + List get props => [mailboxName, countEmailsRead]; +} + +class MarkAsMailboxReadAllFailure extends FeatureFailure { + + MarkAsMailboxReadAllFailure(); + + @override + List get props => []; +} + +class MarkAsMailboxReadFailure extends FeatureFailure { + final dynamic exception; + + MarkAsMailboxReadFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file