diff --git a/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart b/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart index 0423bb0b6..1f04bbc77 100644 --- a/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart +++ b/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart @@ -15,8 +15,6 @@ class RefreshAllMailboxInteractor { Stream> execute(AccountId accountId, jmapState.State currentState) async* { try { - yield Right(LoadingState()); - yield* _mailboxRepository .refresh(accountId, currentState) .map(_toGetMailboxState); diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index db15729b5..aaa913667 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -160,4 +160,66 @@ class ThreadRepositoryImpl extends ThreadRepository { Future _updateState(State newState) async { await stateDataSource.saveState(newState.toStateCache(StateType.email)); } + + @override + Stream refreshChanges( + AccountId accountId, + State currentState, + { + Set? sort, + Properties? propertiesCreated, + Properties? propertiesUpdated, + MailboxId? inMailboxId + } + ) async* { + final localEmailList = await mapDataSource[DataSourceType.local]!.getAllEmailCache( + inMailboxId: inMailboxId, + sort: sort); + + EmailChangeResponse? emailChangeResponse; + bool hasMoreChanges = true; + State? sinceState = currentState; + + while(hasMoreChanges && sinceState != null) { + final changesResponse = await mapDataSource[DataSourceType.network]!.getChanges( + accountId, + sinceState, + propertiesCreated: propertiesCreated, + propertiesUpdated: propertiesUpdated); + + hasMoreChanges = changesResponse.hasMoreChanges; + sinceState = changesResponse.newStateChanges; + + if (emailChangeResponse != null) { + emailChangeResponse.union(changesResponse); + } else { + emailChangeResponse = changesResponse; + } + } + + if (emailChangeResponse != null) { + final newEmailUpdated = await _combineEmailCache( + emailUpdated: emailChangeResponse.updated, + updatedProperties: emailChangeResponse.updatedProperties, + emailCacheList: localEmailList); + + await _updateEmailCache( + newCreated: emailChangeResponse.created, + newUpdated: newEmailUpdated, + newDestroyed: emailChangeResponse.destroyed); + + if (emailChangeResponse.newStateEmail != null) { + await _updateState(emailChangeResponse.newStateEmail!); + } + } + + final newEmailResponse = await Future.wait([ + mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: inMailboxId, sort: sort), + stateDataSource.getState(StateType.email) + ]).then((List response) { + return EmailResponse(emailList: response.first, state: response.last); + }); + + yield newEmailResponse; + } } \ 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 91b9f3ec9..317c470a3 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -2,6 +2,7 @@ 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/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; @@ -19,4 +20,15 @@ abstract class ThreadRepository { MailboxId? inMailboxId } ); + + Stream refreshChanges( + AccountId accountId, + jmap.State currentState, + { + Set? sort, + Properties? propertiesCreated, + Properties? propertiesUpdated, + MailboxId? inMailboxId + } + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart b/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart index 2a0006987..b682bb047 100644 --- a/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart +++ b/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart @@ -17,8 +17,6 @@ class MarkAsMultipleEmailReadInteractor { ReadActions readAction ) async* { try { - yield Right(LoadingState()); - final listEmailNeedMarkAsRead = emails .where((email) => readAction == ReadActions.markAsUnread ? email.isReadEmail() : email.isUnReadEmail()) .toList(); diff --git a/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart b/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart index 36b1c6b6a..5d9f67d9d 100644 --- a/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart +++ b/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart @@ -17,8 +17,6 @@ class MarkAsStarMultipleEmailInteractor { MarkStarAction markStarAction ) async* { try { - yield Right(LoadingState()); - final listEmailNeedMarkStar = emails .where((email) => markStarAction == MarkStarAction.unMarkStar ? email.isFlaggedEmail() : !email.isFlaggedEmail()) .toList(); diff --git a/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart b/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart index 8e5237bc4..2c7ac6c83 100644 --- a/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart +++ b/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart @@ -14,8 +14,6 @@ class MoveMultipleEmailToMailboxInteractor { Stream> execute(AccountId accountId, MoveRequest moveRequest) async* { try { - yield Right(LoadingState()); - final result = await _emailRepository.moveToMailbox(accountId, moveRequest); if (moveRequest.emailIds.length == result.length) { diff --git a/lib/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart b/lib/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart new file mode 100644 index 000000000..8f38dbb50 --- /dev/null +++ b/lib/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart @@ -0,0 +1,51 @@ +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/properties/properties.dart'; +import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; +import 'package:model/model.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; + +class RefreshChangesEmailsInMailboxInteractor { + final ThreadRepository threadRepository; + + RefreshChangesEmailsInMailboxInteractor(this.threadRepository); + + Stream> execute( + AccountId accountId, + jmap.State currentState, + { + Set? sort, + Properties? propertiesCreated, + Properties? propertiesUpdated, + MailboxId? inMailboxId + } + ) async* { + try { + yield* threadRepository + .refreshChanges( + accountId, + currentState, + sort: sort, + propertiesCreated: propertiesCreated, + propertiesUpdated: propertiesUpdated, + inMailboxId: inMailboxId) + .map(_toGetEmailState); + } catch (e) { + yield Left(GetAllEmailFailure(e)); + } + } + + Either _toGetEmailState(EmailResponse emailResponse) { + final presentationEmailList = emailResponse.emailList + ?.map((email) => email.toPresentationEmail()).toList() ?? List.empty(); + + return Right(GetAllEmailSuccess( + emailList: presentationEmailList, + currentEmailState: emailResponse.state)); + } +} \ 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 56597edcc..a7264ab96 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mail import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; class ThreadBindings extends Bindings { @@ -54,6 +55,7 @@ class ThreadBindings extends Bindings { Get.lazyPut(() => MoveMultipleEmailToMailboxInteractor(Get.find())); Get.lazyPut(() => MarkAsStarEmailInteractor(Get.find())); Get.lazyPut(() => MarkAsStarMultipleEmailInteractor(Get.find())); + Get.lazyPut(() => RefreshChangesEmailsInMailboxInteractor(Get.find())); Get.put(ThreadController( Get.find(), Get.find(), @@ -63,6 +65,7 @@ class ThreadBindings extends Bindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index eacc6478b..b828d0d0a 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.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'; @@ -30,6 +29,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mail import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/load_more_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; @@ -47,6 +47,7 @@ class ThreadController extends BaseController { final MoveMultipleEmailToMailboxInteractor _moveMultipleEmailToMailboxInteractor; final MarkAsStarEmailInteractor _markAsStarEmailInteractor; final MarkAsStarMultipleEmailInteractor _markAsStarMultipleEmailInteractor; + final RefreshChangesEmailsInMailboxInteractor _refreshChangesEmailsInMailboxInteractor; final emailList = [].obs; final loadMoreState = LoadMoreState.IDLE.obs; @@ -64,6 +65,8 @@ class ThreadController extends BaseController { ..add(EmailComparator(EmailComparatorProperty.sentAt) ..setIsAscending(false)); + AccountId? get _accountId => mailboxDashBoardController.accountId.value; + ThreadController( this.responsiveUtils, this._getEmailsInMailboxInteractor, @@ -73,6 +76,7 @@ class ThreadController extends BaseController { this._moveMultipleEmailToMailboxInteractor, this._markAsStarEmailInteractor, this._markAsStarMultipleEmailInteractor, + this._refreshChangesEmailsInMailboxInteractor, ); @override @@ -96,14 +100,11 @@ class ThreadController extends BaseController { state.map((success) { if (success is MarkAsEmailReadSuccess || success is MarkAsMultipleEmailReadAllSuccess - || success is MarkAsMultipleEmailReadHasSomeEmailFailure) { - _refreshListEmail(); - mailboxDashBoardController.clearState(); - } else if (success is MoveToMailboxSuccess) { - _refreshListEmail(); - mailboxDashBoardController.clearState(); - } else if (success is MarkAsStarEmailSuccess) { - _refreshListEmail(); + || success is MarkAsMultipleEmailReadHasSomeEmailFailure + || success is MoveToMailboxSuccess + || success is MarkAsStarEmailSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(); mailboxDashBoardController.clearState(); } }); @@ -163,10 +164,8 @@ class ThreadController extends BaseController { void onError(error) {} void _getAllEmail() { - final accountId = mailboxDashBoardController.accountId.value; - - if (accountId != null) { - _getAllEmailAction(accountId, inMailboxId: _currentMailboxId); + if (_accountId != null) { + _getAllEmailAction(_accountId!, inMailboxId: _currentMailboxId); } } @@ -192,13 +191,6 @@ class ThreadController extends BaseController { } } - void _resetPositionCurrentAndLoadMoreState() { - if (loadMoreState.value == LoadMoreState.LOADING) { - _currentPosition -= _totalNumberOfEmails; - } - loadMoreState.value = LoadMoreState.IDLE; - } - void _getAllEmailAction(AccountId accountId, {MailboxId? inMailboxId}) { consumeState(_getEmailsInMailboxInteractor.execute( accountId, @@ -212,15 +204,23 @@ class ThreadController extends BaseController { )); } - void refreshGetAllEmailAction() { + void refreshAllEmail() { + dispatchState(Right(LoadingState())); loadMoreState.value = LoadMoreState.IDLE; _currentPosition = 0; - dispatchState(Right(LoadingState())); + _getAllEmail(); + } - final accountId = mailboxDashBoardController.accountId.value; - - if (accountId != null) { - _getAllEmailAction(accountId); + void _refreshEmailChanges() { + if (_accountId != null && _currentEmailState != null) { + consumeState(_refreshChangesEmailsInMailboxInteractor.execute( + _accountId!, + _currentEmailState!, + sort: _sortOrder, + propertiesCreated: ThreadConstants.propertiesDefault, + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, + inMailboxId: _currentMailboxId, + )); } } @@ -228,13 +228,18 @@ class ThreadController extends BaseController { loadMoreState.value = LoadMoreState.LOADING; _currentPosition += _totalNumberOfEmails; - final accountId = mailboxDashBoardController.accountId.value; - - if (accountId != null) { - _getAllEmailAction(accountId); + if (_accountId != null) { + _getAllEmailAction(_accountId!); } } + void _resetPositionCurrentAndLoadMoreState() { + if (loadMoreState.value == LoadMoreState.LOADING) { + _currentPosition -= _totalNumberOfEmails; + } + loadMoreState.value = LoadMoreState.IDLE; + } + SelectMode getSelectMode(PresentationEmail presentationEmail, PresentationEmail? selectedEmail) { return presentationEmail.id == selectedEmail?.id ? SelectMode.ACTIVE @@ -272,28 +277,6 @@ class ThreadController extends BaseController { currentSelectMode.value = SelectMode.INACTIVE; } - void _refreshListEmail() { - currentSelectMode.value = SelectMode.INACTIVE; - final newLimit = emailList.isNotEmpty ? UnsignedInt(emailList.length) : ThreadConstants.defaultLimit; - loadMoreState.value = LoadMoreState.IDLE; - dispatchState(Right(LoadingState())); - - final accountId = mailboxDashBoardController.accountId.value; - - if (accountId != null) { - consumeState(_getEmailsInMailboxInteractor.execute( - accountId, - limit: newLimit, - position: 0, - sort: _sortOrder, - filter: _filterCondition, - propertiesCreated: ThreadConstants.propertiesDefault, - propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - inMailboxId: _currentMailboxId - )); - } - } - void markAsSelectedEmailRead(List listPresentationEmail, {bool fromContextMenuAction = false}) { if (fromContextMenuAction) { popBack(); @@ -301,11 +284,10 @@ class ThreadController extends BaseController { final readAction = isAllEmailRead(listPresentationEmail) ? ReadActions.markAsUnread : ReadActions.markAsRead; - final accountId = mailboxDashBoardController.accountId.value; final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value; - if (accountId != null && mailboxCurrent != null) { + if (_accountId != null && mailboxCurrent != null) { final listEmail = listPresentationEmail.map((presentationEmail) => presentationEmail.toEmail()).toList(); - consumeState(_markAsMultipleEmailReadInteractor.execute(accountId, listEmail, readAction)); + consumeState(_markAsMultipleEmailReadInteractor.execute(_accountId!, listEmail, readAction)); } } @@ -345,19 +327,18 @@ class ThreadController extends BaseController { void moveSelectedMultipleEmailToMailboxAction(List listEmail) async { final currentMailbox = mailboxDashBoardController.selectedMailbox.value; - final accountId = mailboxDashBoardController.accountId.value; - if (currentMailbox != null && accountId != null) { + if (currentMailbox != null && _accountId != null) { popBack(); final listEmailIds = listEmail.map((email) => email.id).toList(); final destinationMailbox = await push( AppRoutes.DESTINATION_PICKER, - arguments: DestinationPickerArguments(accountId, listEmailIds, currentMailbox) + arguments: DestinationPickerArguments(_accountId!, listEmailIds, currentMailbox) ); if (destinationMailbox != null && destinationMailbox is PresentationMailbox) { _moveSelectedEmailMultipleToMailbox( - accountId, + _accountId!, MoveRequest( listEmailIds, currentMailbox.id, @@ -373,6 +354,7 @@ class ThreadController extends BaseController { } void _moveSelectedMultipleEmailToMailboxSuccess(Success success) { + cancelSelectEmail(); mailboxDashBoardController.dispatchState(Right(success)); String? destinationPath; @@ -416,14 +398,12 @@ class ThreadController extends BaseController { ); } - _refreshListEmail(); + _refreshEmailChanges(); } void _undoMoveSelectedMultipleEmailToMailbox(MoveRequest moveRequest) { - final accountId = mailboxDashBoardController.accountId.value; - - if (accountId != null) { - _moveSelectedEmailMultipleToMailbox(accountId, moveRequest); + if (_accountId != null) { + _moveSelectedEmailMultipleToMailbox(_accountId!, moveRequest); } } @@ -438,32 +418,31 @@ class ThreadController extends BaseController { } void markAsStarEmail(PresentationEmail presentationEmail) { - final accountId = mailboxDashBoardController.accountId.value; final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value; - if (accountId != null && mailboxCurrent != null) { + if (_accountId != null && mailboxCurrent != null) { final importantAction = presentationEmail.isFlaggedEmail() ? MarkStarAction.unMarkStar : MarkStarAction.markStar; dispatchState(Right(LoadingState())); - consumeState(_markAsStarEmailInteractor.execute(accountId, presentationEmail.toEmail(), importantAction)); + consumeState(_markAsStarEmailInteractor.execute(_accountId!, presentationEmail.toEmail(), importantAction)); } } void _markAsStarEmailSuccess(Success success) { - _refreshListEmail(); + _refreshEmailChanges(); } void markAsStarSelectedMultipleEmail(List listPresentationEmail, MarkStarAction markStarAction) { popBack(); - final accountId = mailboxDashBoardController.accountId.value; final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value; - if (accountId != null && mailboxCurrent != null) { + if (_accountId != null && mailboxCurrent != null) { final listEmail = listPresentationEmail.map((presentationEmail) => presentationEmail.toEmail()).toList(); - consumeState(_markAsStarMultipleEmailInteractor.execute(accountId, listEmail, markStarAction)); + consumeState(_markAsStarMultipleEmailInteractor.execute(_accountId!, listEmail, markStarAction)); } } void _markAsStarMultipleEmailSuccess(Success success) { cancelSelectEmail(); + _refreshEmailChanges(); MarkStarAction? markStarAction; int countMarkStarSuccess = 0; @@ -481,8 +460,6 @@ class ThreadController extends BaseController { ? AppLocalizations.of(Get.context!).marked_unstar_multiple_item(countMarkStarSuccess) : AppLocalizations.of(Get.context!).marked_star_multiple_item(countMarkStarSuccess)); } - - _refreshListEmail(); } void _markAsStarMultipleEmailFailure(Failure failure) { diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index aa5f6a9d1..de28d236f 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -217,11 +217,11 @@ class ThreadView extends GetWidget { ? controller.emailList.isNotEmpty ? RefreshIndicator( color: AppColor.primaryColor, - onRefresh: () async => controller.refreshGetAllEmailAction(), + onRefresh: () async => controller.refreshAllEmail(), child: _buildListEmailBody(context, controller.emailList)) : RefreshIndicator( color: AppColor.primaryColor, - onRefresh: () async => controller.refreshGetAllEmailAction(), + onRefresh: () async => controller.refreshAllEmail(), child: _buildEmptyEmail(context)) : controller.emailList.isNotEmpty ? _buildListEmailBody(context, controller.emailList)