diff --git a/core/lib/presentation/state/success.dart b/core/lib/presentation/state/success.dart index a94af9c70..e88d408b7 100644 --- a/core/lib/presentation/state/success.dart +++ b/core/lib/presentation/state/success.dart @@ -16,6 +16,20 @@ class UIState extends ViewState { class LoadingState extends UIState { LoadingState(); + @override + List get props => []; +} + +class LoadingMoreState extends UIState { + LoadingMoreState(); + + @override + List get props => []; +} + +class RefreshingState extends UIState { + RefreshingState(); + @override List get props => []; } \ No newline at end of file diff --git a/lib/features/caching/caching_manager.dart b/lib/features/caching/caching_manager.dart index 6e7cab0b7..bc33b7362 100644 --- a/lib/features/caching/caching_manager.dart +++ b/lib/features/caching/caching_manager.dart @@ -1,4 +1,5 @@ +import 'package:tmail_ui_user/features/caching/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/mailbox_cache_client.dart'; import 'package:tmail_ui_user/features/caching/state_cache_client.dart'; @@ -6,13 +7,19 @@ class CachingManager { final MailboxCacheClient _mailboxCacheClient; final StateCacheClient _stateCacheClient; + final EmailCacheClient _emailCacheClient; - CachingManager(this._mailboxCacheClient, this._stateCacheClient); + CachingManager( + this._mailboxCacheClient, + this._stateCacheClient, + this._emailCacheClient + ); Future clearAll() async { await Future.wait([ _stateCacheClient.deleteBox(), _mailboxCacheClient.deleteBox(), + _emailCacheClient.deleteBox(), ]); } } \ No newline at end of file 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 1f04bbc77..ed84b5ca4 100644 --- a/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart +++ b/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart @@ -15,6 +15,8 @@ class RefreshAllMailboxInteractor { Stream> execute(AccountId accountId, jmapState.State currentState) async* { try { + yield Right(RefreshingState()); + yield* _mailboxRepository .refresh(accountId, currentState) .map(_toGetMailboxState); diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index 1d9ee8409..053396e85 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -10,10 +10,9 @@ import 'package:tmail_ui_user/features/thread/data/model/email_change_response.d import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; abstract class ThreadDataSource { - Future getAllEmail( + Future getAllEmail( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, Filter? filter, diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 91a777f14..0799fc8bb 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -18,10 +18,9 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { LocalThreadDataSourceImpl(this._emailCacheManager); @override - Future getAllEmail( + Future getAllEmail( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, Filter? filter, diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index ab73b8f20..0c37f90f9 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -18,23 +18,18 @@ class ThreadDataSourceImpl extends ThreadDataSource { ThreadDataSourceImpl(this.threadAPI); @override - Future getAllEmail( + Future getAllEmail( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, Filter? filter, Properties? properties, - Properties? propertiesCreated, - Properties? propertiesUpdated, - MailboxId? inMailboxId } ) { return Future.sync(() async { return await threadAPI.getAllEmail( accountId, - position: position, limit: limit, sort: sort, filter: filter, @@ -65,7 +60,7 @@ class ThreadDataSourceImpl extends ThreadDataSource { } @override - Future> getAllEmailCache({MailboxId? inMailboxId, Set? sort}) { + Future> getAllEmailCache({MailboxId? inMailboxId, Set? sort, UnsignedInt? limit}) { throw UnimplementedError(); } diff --git a/lib/features/thread/data/model/email_change_response.dart b/lib/features/thread/data/model/email_change_response.dart index 8e6889b3f..e34ab93f9 100644 --- a/lib/features/thread/data/model/email_change_response.dart +++ b/lib/features/thread/data/model/email_change_response.dart @@ -25,9 +25,9 @@ class EmailChangeResponse with EquatableMixin { }); EmailChangeResponse union(EmailChangeResponse other) => EmailChangeResponse( - updated: updated.union(other.updated), - created: created.union(other.created), - destroyed: destroyed.union(other.destroyed), + updated: updated.unite(other.updated), + created: created.unite(other.created), + destroyed: destroyed.unite(other.destroyed), newStateEmail: other.newStateEmail, newStateChanges: other.newStateChanges, hasMoreChanges: other.hasMoreChanges, diff --git a/lib/features/thread/data/network/thread_api.dart b/lib/features/thread/data/network/thread_api.dart index acfe84607..431f5cea3 100644 --- a/lib/features/thread/data/network/thread_api.dart +++ b/lib/features/thread/data/network/thread_api.dart @@ -24,10 +24,9 @@ class ThreadAPI { ThreadAPI(this.httpClient); - Future getAllEmail( + Future getAllEmail( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, Filter? filter, @@ -40,8 +39,6 @@ class ThreadAPI { final queryEmailMethod = QueryEmailMethod(accountId); - if (position != null) queryEmailMethod..addPosition(position); - if (limit != null) queryEmailMethod..addLimit(limit); if (sort != null) queryEmailMethod..addSorts(sort); @@ -74,7 +71,7 @@ class ThreadAPI { }); } - return EmailResponse(emailList: resultList?.list, state: resultList?.state); + return EmailsResponse(emailList: resultList?.list, state: resultList?.state); } Future getChanges( diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index aaa913667..c736df5bd 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -25,34 +25,31 @@ class ThreadRepositoryImpl extends ThreadRepository { ThreadRepositoryImpl(this.mapDataSource, this.stateDataSource); @override - Stream getAllEmail( + Stream getAllEmail( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, - Filter? filter, + EmailFilter? emailFilter, Properties? propertiesCreated, Properties? propertiesUpdated, - MailboxId? inMailboxId } ) async* { final localEmailResponse = await Future.wait([ - mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: inMailboxId, sort: sort), + mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: emailFilter?.mailboxId, sort: sort), stateDataSource.getState(StateType.email) ]).then((List response) { - return EmailResponse(emailList: response.first, state: response.last); + return EmailsResponse(emailList: response.first, state: response.last); }); - EmailResponse? networkEmailResponse; + EmailsResponse? networkEmailResponse; if (!localEmailResponse.hasEmails()) { networkEmailResponse = await mapDataSource[DataSourceType.network]!.getAllEmail( accountId, - position: position, limit: limit, sort: sort, - filter: filter, + filter: emailFilter?.filter, properties: propertiesCreated); yield networkEmailResponse; @@ -113,10 +110,10 @@ class ThreadRepositoryImpl extends ThreadRepository { } final newEmailResponse = await Future.wait([ - mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: inMailboxId, sort: sort), + mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: emailFilter?.mailboxId, sort: sort), stateDataSource.getState(StateType.email) ]).then((List response) { - return EmailResponse(emailList: response.first, state: response.last); + return EmailsResponse(emailList: response.first, state: response.last); }); yield newEmailResponse; @@ -132,7 +129,7 @@ class ThreadRepositoryImpl extends ThreadRepository { if (updatedProperties == null) { return email; } else { - final emailOld = emailCacheList?.findEmail(email.id); + final emailOld = emailCacheList?.findEmailById(email.id); if (emailOld != null) { return emailOld.combineEmail(email, updatedProperties); } else { @@ -162,10 +159,11 @@ class ThreadRepositoryImpl extends ThreadRepository { } @override - Stream refreshChanges( + Stream refreshChanges( AccountId accountId, State currentState, { + UnsignedInt? limit, Set? sort, Properties? propertiesCreated, Properties? propertiesUpdated, @@ -173,8 +171,8 @@ class ThreadRepositoryImpl extends ThreadRepository { } ) async* { final localEmailList = await mapDataSource[DataSourceType.local]!.getAllEmailCache( - inMailboxId: inMailboxId, - sort: sort); + inMailboxId: inMailboxId, + sort: sort); EmailChangeResponse? emailChangeResponse; bool hasMoreChanges = true; @@ -217,9 +215,38 @@ class ThreadRepositoryImpl extends ThreadRepository { mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: inMailboxId, sort: sort), stateDataSource.getState(StateType.email) ]).then((List response) { - return EmailResponse(emailList: response.first, state: response.last); + return EmailsResponse(emailList: response.first, state: response.last); }); yield newEmailResponse; } + + @override + Stream loadMoreEmails( + AccountId accountId, + { + int? position, + UnsignedInt? limit, + Set? sort, + Filter? filter, + Properties? properties, + } + ) async* { + final emailResponse = await mapDataSource[DataSourceType.network]!.getAllEmail( + accountId, + limit: limit, + sort: sort, + filter: filter, + properties: properties); + + final newEmailList = emailResponse.emailList != null && emailResponse.emailList!.isNotEmpty + ? emailResponse.emailList!.sublist(1) + : []; + + if (newEmailList.isNotEmpty) { + await _updateEmailCache(newCreated: newEmailList); + } + + yield EmailsResponse(emailList: newEmailList, state: emailResponse.state); + } } \ No newline at end of file diff --git a/lib/features/thread/domain/model/email_response.dart b/lib/features/thread/domain/model/email_response.dart index e4681b022..87c054e41 100644 --- a/lib/features/thread/domain/model/email_response.dart +++ b/lib/features/thread/domain/model/email_response.dart @@ -3,11 +3,11 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -class EmailResponse with EquatableMixin { +class EmailsResponse with EquatableMixin { final List? emailList; final State? state; - EmailResponse({ + EmailsResponse({ this.emailList, this.state }); diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index 317c470a3..384a2f5bd 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -5,23 +5,22 @@ 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:model/model.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; abstract class ThreadRepository { - Stream getAllEmail( + Stream getAllEmail( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, - Filter? filter, + EmailFilter? emailFilter, Properties? propertiesCreated, Properties? propertiesUpdated, - MailboxId? inMailboxId } ); - Stream refreshChanges( + Stream refreshChanges( AccountId accountId, jmap.State currentState, { @@ -31,4 +30,14 @@ abstract class ThreadRepository { MailboxId? inMailboxId } ); + + Stream loadMoreEmails( + AccountId accountId, + { + UnsignedInt? limit, + Set? sort, + Filter? filter, + Properties? properties, + } + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/load_more_emails_state.dart b/lib/features/thread/domain/state/load_more_emails_state.dart new file mode 100644 index 000000000..f5702bfb9 --- /dev/null +++ b/lib/features/thread/domain/state/load_more_emails_state.dart @@ -0,0 +1,20 @@ +import 'package:core/core.dart'; +import 'package:model/model.dart'; + +class LoadMoreEmailsSuccess extends UIState { + final List emailList; + + LoadMoreEmailsSuccess(this.emailList); + + @override + List get props => [emailList]; +} + +class LoadMoreEmailsFailure extends FeatureFailure { + final exception; + + LoadMoreEmailsFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart b/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart index 1e825843e..c67405a22 100644 --- a/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart +++ b/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart @@ -1,11 +1,9 @@ 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/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/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'; 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'; @@ -19,13 +17,11 @@ class GetEmailsInMailboxInteractor { Stream> execute( AccountId accountId, { - int? position, UnsignedInt? limit, Set? sort, - Filter? filter, + EmailFilter? emailFilter, Properties? propertiesCreated, Properties? propertiesUpdated, - MailboxId? inMailboxId } ) async* { try { @@ -34,20 +30,18 @@ class GetEmailsInMailboxInteractor { yield* threadRepository .getAllEmail( accountId, - position: position, limit: limit, sort: sort, - filter: filter, + emailFilter: emailFilter, propertiesCreated: propertiesCreated, - propertiesUpdated: propertiesUpdated, - inMailboxId: inMailboxId) + propertiesUpdated: propertiesUpdated) .map(_toGetEmailState); } catch (e) { yield Left(GetAllEmailFailure(e)); } } - Either _toGetEmailState(EmailResponse emailResponse) { + Either _toGetEmailState(EmailsResponse emailResponse) { final presentationEmailList = emailResponse.emailList ?.map((email) => email.toPresentationEmail()).toList() ?? List.empty(); diff --git a/lib/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart b/lib/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart new file mode 100644 index 000000000..d0f16cf8e --- /dev/null +++ b/lib/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart @@ -0,0 +1,49 @@ +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/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/unsigned_int.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:model/model.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; + +class LoadMoreEmailsInMailboxInteractor { + final ThreadRepository threadRepository; + + LoadMoreEmailsInMailboxInteractor(this.threadRepository); + + Stream> execute( + AccountId accountId, + { + UnsignedInt? limit, + Set? sort, + Filter? filter, + Properties? properties + } + ) async* { + try { + yield Right(LoadingMoreState()); + + yield* threadRepository + .loadMoreEmails( + accountId, + limit: limit, + sort: sort, + filter: filter, + properties: properties) + .map(_toGetEmailState); + } catch (e) { + yield Left(LoadMoreEmailsFailure(e)); + } + } + + Either _toGetEmailState(EmailsResponse emailResponse) { + final presentationEmailList = emailResponse.emailList + ?.map((email) => email.toPresentationEmail()).toList() ?? List.empty(); + + return Right(LoadMoreEmailsSuccess(presentationEmailList)); + } +} \ No newline at end of file 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 index 8f38dbb50..bfe40ad3d 100644 --- 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 @@ -25,6 +25,8 @@ class RefreshChangesEmailsInMailboxInteractor { MailboxId? inMailboxId } ) async* { + yield Right(RefreshingState()); + try { yield* threadRepository .refreshChanges( @@ -40,7 +42,7 @@ class RefreshChangesEmailsInMailboxInteractor { } } - Either _toGetEmailState(EmailResponse emailResponse) { + Either _toGetEmailState(EmailsResponse emailResponse) { final presentationEmailList = emailResponse.emailList ?.map((email) => email.toPresentationEmail()).toList() ?? List.empty(); diff --git a/lib/features/thread/presentation/model/load_more_state.dart b/lib/features/thread/presentation/model/load_more_state.dart deleted file mode 100644 index f1b3c9c74..000000000 --- a/lib/features/thread/presentation/model/load_more_state.dart +++ /dev/null @@ -1,6 +0,0 @@ - -enum LoadMoreState { - IDLE, - LOADING, - COMPLETED -} \ 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 a7264ab96..034d42224 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -21,6 +21,7 @@ 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/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'; 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'; @@ -56,6 +57,7 @@ class ThreadBindings extends Bindings { Get.lazyPut(() => MarkAsStarEmailInteractor(Get.find())); Get.lazyPut(() => MarkAsStarMultipleEmailInteractor(Get.find())); Get.lazyPut(() => RefreshChangesEmailsInMailboxInteractor(Get.find())); + Get.lazyPut(() => LoadMoreEmailsInMailboxInteractor(Get.find())); Get.put(ThreadController( Get.find(), Get.find(), @@ -66,6 +68,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 b828d0d0a..3445c1312 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -22,15 +22,16 @@ import 'package:tmail_ui_user/features/email/presentation/model/composer_argumen import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.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'; 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/mark_as_star_multiple_email_state.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'; 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'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; @@ -48,13 +49,12 @@ class ThreadController extends BaseController { final MarkAsStarEmailInteractor _markAsStarEmailInteractor; final MarkAsStarMultipleEmailInteractor _markAsStarMultipleEmailInteractor; final RefreshChangesEmailsInMailboxInteractor _refreshChangesEmailsInMailboxInteractor; + final LoadMoreEmailsInMailboxInteractor _loadMoreEmailsInMailboxInteractor; final emailList = [].obs; - final loadMoreState = LoadMoreState.IDLE.obs; final currentSelectMode = SelectMode.INACTIVE.obs; - int _currentPosition = 0; - int _totalNumberOfEmails = 0; + bool canLoadMore = true; MailboxId? _currentMailboxId; jmap.State? _currentEmailState; @@ -77,6 +77,7 @@ class ThreadController extends BaseController { this._markAsStarEmailInteractor, this._markAsStarMultipleEmailInteractor, this._refreshChangesEmailsInMailboxInteractor, + this._loadMoreEmailsInMailboxInteractor, ); @override @@ -122,20 +123,27 @@ class ThreadController extends BaseController { @override void onData(Either newState) { super.onData(newState); - newState.map((success) { - if (success is GetAllEmailSuccess) { - _getAllEmailSuccess(success); + newState.fold( + (failure) { + if (failure is LoadMoreEmailsFailure) { + canLoadMore = false; + } + }, + (success) { + if (success is GetAllEmailSuccess) { + _getAllEmailSuccess(success); + } else if (success is LoadMoreEmailsSuccess) { + _loadMoreEmailsSuccess(success); + } } - }); + ); } @override void onDone() { viewState.value.fold( (failure) { - if (failure is GetAllEmailFailure) { - _resetPositionCurrentAndLoadMoreState(); - } else if (failure is MarkAsMultipleEmailReadAllFailure + if (failure is MarkAsMultipleEmailReadAllFailure || failure is MarkAsMultipleEmailReadFailure) { _markAsSelectedEmailReadFailure(failure); } else if (failure is MarkAsStarMultipleEmailAllFailure @@ -165,49 +173,36 @@ class ThreadController extends BaseController { void _getAllEmail() { if (_accountId != null) { - _getAllEmailAction(_accountId!, inMailboxId: _currentMailboxId); + _getAllEmailAction(_accountId!, mailboxId: _currentMailboxId); } } void _resetToOriginalValue() { dispatchState(Right(LoadingState())); emailList.value = []; - loadMoreState.value = LoadMoreState.IDLE; - _currentPosition = 0; + canLoadMore = true; } - void _getAllEmailSuccess(Success success) { - if (success is GetAllEmailSuccess) { - _currentEmailState = success.currentEmailState; - - if (loadMoreState.value == LoadMoreState.LOADING) { - emailList.addAll(success.emailList); - } else { - emailList.value = success.emailList; - } - - _totalNumberOfEmails = emailList.length; - loadMoreState.value = success.emailList.isEmpty ? LoadMoreState.COMPLETED : LoadMoreState.IDLE; - } + void _getAllEmailSuccess(GetAllEmailSuccess success) { + _currentEmailState = success.currentEmailState; + emailList.value = success.emailList; } - void _getAllEmailAction(AccountId accountId, {MailboxId? inMailboxId}) { + void _getAllEmailAction(AccountId accountId, {MailboxId? mailboxId}) { consumeState(_getEmailsInMailboxInteractor.execute( accountId, limit: ThreadConstants.defaultLimit, - position: _currentPosition, sort: _sortOrder, - filter: _filterCondition, + emailFilter: EmailFilter( + filter: _filterCondition, + mailboxId: mailboxId ?? _currentMailboxId), propertiesCreated: ThreadConstants.propertiesDefault, propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - inMailboxId: inMailboxId ?? _currentMailboxId )); } void refreshAllEmail() { dispatchState(Right(LoadingState())); - loadMoreState.value = LoadMoreState.IDLE; - _currentPosition = 0; _getAllEmail(); } @@ -224,20 +219,28 @@ class ThreadController extends BaseController { } } - void loadMoreEmailAction() { - loadMoreState.value = LoadMoreState.LOADING; - _currentPosition += _totalNumberOfEmails; - - if (_accountId != null) { - _getAllEmailAction(_accountId!); + void loadMoreEmails() { + if (canLoadMore) { + if (_accountId != null) { + consumeState(_loadMoreEmailsInMailboxInteractor.execute( + _accountId!, + limit: ThreadConstants.defaultLimit, + sort: _sortOrder, + filter: EmailFilterCondition( + inMailbox: mailboxDashBoardController.selectedMailbox.value?.id, + before: emailList.last.receivedAt), + properties: ThreadConstants.propertiesDefault, + )); + } } } - void _resetPositionCurrentAndLoadMoreState() { - if (loadMoreState.value == LoadMoreState.LOADING) { - _currentPosition -= _totalNumberOfEmails; + void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) { + if (success.emailList.isNotEmpty) { + emailList.addAll(success.emailList); + } else { + canLoadMore = false; } - loadMoreState.value = LoadMoreState.IDLE; } SelectMode getSelectMode(PresentationEmail presentationEmail, PresentationEmail? selectedEmail) { @@ -421,7 +424,6 @@ class ThreadController extends BaseController { final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value; if (_accountId != null && mailboxCurrent != null) { final importantAction = presentationEmail.isFlaggedEmail() ? MarkStarAction.unMarkStar : MarkStarAction.markStar; - dispatchState(Right(LoadingState())); consumeState(_markAsStarEmailInteractor.execute(_accountId!, presentationEmail.toEmail(), importantAction)); } } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index de28d236f..36a627e7e 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -5,7 +5,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/model.dart'; -import 'package:tmail_ui_user/features/thread/presentation/model/load_more_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/app_bar_thread_select_mode_active_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/app_bar_thread_widget_builder.dart'; @@ -184,7 +183,7 @@ class ThreadView extends GetWidget { Widget _buildLoadingView() { return Obx(() => controller.viewState.value.fold( (failure) => SizedBox.shrink(), - (success) => success is LoadingState && controller.loadMoreState.value != LoadMoreState.LOADING + (success) => success is LoadingState ? Center(child: Padding( padding: EdgeInsets.only(top: 16, bottom: 16), child: SizedBox( @@ -195,14 +194,16 @@ class ThreadView extends GetWidget { } Widget _buildLoadingViewLoadMore() { - return Obx(() => controller.loadMoreState.value == LoadMoreState.LOADING - ? Center(child: Padding( - padding: EdgeInsets.only(top: 16, bottom: 16), - child: SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator(color: AppColor.primaryColor)))) - : SizedBox.shrink()); + return Obx(() => controller.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) => success is LoadingMoreState + ? Center(child: Padding( + padding: EdgeInsets.only(top: 16, bottom: 16), + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(color: AppColor.primaryColor)))) + : SizedBox.shrink())); } Widget _buildListEmail(BuildContext context) { @@ -232,9 +233,8 @@ class ThreadView extends GetWidget { return NotificationListener( onNotification: (ScrollNotification scrollInfo) { if (scrollInfo is ScrollEndNotification - && controller.loadMoreState.value == LoadMoreState.IDLE && scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { - controller.loadMoreEmailAction(); + controller.loadMoreEmails(); } return false; }, diff --git a/lib/main/bindings/local/local_bindings.dart b/lib/main/bindings/local/local_bindings.dart index 72ede4d55..1978193d6 100644 --- a/lib/main/bindings/local/local_bindings.dart +++ b/lib/main/bindings/local/local_bindings.dart @@ -24,10 +24,11 @@ class LocalBindings extends Bindings { Get.put(MailboxCacheClient()); Get.put(StateCacheClient()); Get.put(MailboxCacheManager(Get.find())); - Get.put(CachingManager( - Get.find(), - Get.find())); Get.put(EmailCacheClient()); Get.put(EmailCacheManager(Get.find())); + Get.put(CachingManager( + Get.find(), + Get.find(), + Get.find())); } } \ No newline at end of file diff --git a/model/lib/email/email_filter.dart b/model/lib/email/email_filter.dart new file mode 100644 index 000000000..857b2485d --- /dev/null +++ b/model/lib/email/email_filter.dart @@ -0,0 +1,15 @@ + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; + +class EmailFilter with EquatableMixin { + + final Filter? filter; + final MailboxId? mailboxId; + + EmailFilter({this.filter, this.mailboxId}); + + @override + List get props => [filter, mailboxId]; +} \ No newline at end of file diff --git a/model/lib/extensions/list_email_extension.dart b/model/lib/extensions/list_email_extension.dart index fc887ca3a..fb6e00511 100644 --- a/model/lib/extensions/list_email_extension.dart +++ b/model/lib/extensions/list_email_extension.dart @@ -8,7 +8,7 @@ import 'package:jmap_dart_client/jmap/core/extensions/unsigned_int_extension.dar extension ListEmailExtension on List { - Email? findEmail(EmailId emailId) { + Email? findEmailById(EmailId emailId) { try { return firstWhere((email) => email.id == emailId); } catch (e) { diff --git a/model/lib/extensions/list_extension.dart b/model/lib/extensions/list_extension.dart index 4ff837340..9ab054a1e 100644 --- a/model/lib/extensions/list_extension.dart +++ b/model/lib/extensions/list_extension.dart @@ -1,7 +1,7 @@ extension ListExtension on List? { - List? union(List? other) { + List? unite(List? other) { if (other != null) { this?.addAll(other); } diff --git a/model/lib/model.dart b/model/lib/model.dart index c6a8494ab..a239b312f 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -28,6 +28,7 @@ export 'email/attachment.dart'; export 'email/move_action.dart'; export 'email/mark_star_action.dart'; export 'email/email_property.dart'; +export 'email/email_filter.dart'; // Extensions export 'extensions/email_address_extension.dart'; diff --git a/test/features/mailbox/domain/usecases/refresh_all_mailbox_interactor_test.dart b/test/features/mailbox/domain/usecases/refresh_all_mailbox_interactor_test.dart index af385500a..97051643c 100644 --- a/test/features/mailbox/domain/usecases/refresh_all_mailbox_interactor_test.dart +++ b/test/features/mailbox/domain/usecases/refresh_all_mailbox_interactor_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; @@ -45,8 +46,9 @@ void main() { final states = await streamStates.toList(); - expect(states.length, equals(1)); + expect(states.length, equals(2)); expect(states, containsAllInOrder({ + Right(RefreshingState()), Right(GetAllMailboxSuccess( defaultMailboxList: { MailboxFixtures.inboxMailbox.toPresentationMailbox(), diff --git a/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart b/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart index 71162f8f3..ab622f773 100644 --- a/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart +++ b/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart @@ -38,20 +38,21 @@ void main() { when(threadRepository.getAllEmail( AccountFixtures.aliceAccountId, limit: UnsignedInt(20), - position: 0, sort: Set()..add(EmailComparator(EmailComparatorProperty.sentAt)..setIsAscending(false)), - filter: EmailFilterCondition(inMailbox: MailboxFixtures.inboxMailbox.id), + emailFilter: EmailFilter( + filter: EmailFilterCondition(inMailbox: MailboxFixtures.inboxMailbox.id), + mailboxId: MailboxFixtures.inboxMailbox.id, + ), propertiesCreated: ThreadConstants.propertiesDefault, propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - inMailboxId: MailboxFixtures.inboxMailbox.id )).thenAnswer((_) => Stream.fromIterable({ - EmailResponse( + EmailsResponse( emailList: { EmailFixtures.email1, EmailFixtures.email2 }.toList(), state: jmap.State('s1')), - EmailResponse( + EmailsResponse( emailList: { EmailFixtures.email1, EmailFixtures.email2, @@ -65,12 +66,13 @@ void main() { final streamStates = getEmailsInMailboxInteractor.execute( AccountFixtures.aliceAccountId, limit: UnsignedInt(20), - position: 0, sort: Set()..add(EmailComparator(EmailComparatorProperty.sentAt)..setIsAscending(false)), - filter: EmailFilterCondition(inMailbox: MailboxFixtures.inboxMailbox.id), + emailFilter: EmailFilter( + filter: EmailFilterCondition(inMailbox: MailboxFixtures.inboxMailbox.id), + mailboxId: MailboxFixtures.inboxMailbox.id, + ), propertiesCreated: ThreadConstants.propertiesDefault, - propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - inMailboxId: MailboxFixtures.inboxMailbox.id); + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault); final states = await streamStates.toList(); diff --git a/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.mocks.dart b/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.mocks.dart index c94bffcdf..6886aca01 100644 --- a/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.mocks.dart +++ b/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.mocks.dart @@ -5,13 +5,14 @@ import 'dart:async' as _i3; import 'package:jmap_dart_client/jmap/account_id.dart' as _i5; -import 'package:jmap_dart_client/jmap/core/filter/filter.dart' as _i8; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart' as _i12; import 'package:jmap_dart_client/jmap/core/properties/properties.dart' as _i9; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart' as _i7; -import 'package:jmap_dart_client/jmap/core/state.dart' as _i11; +import 'package:jmap_dart_client/jmap/core/state.dart' as _i10; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart' as _i6; -import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart' as _i10; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart' as _i11; import 'package:mockito/mockito.dart' as _i1; +import 'package:model/email/email_filter.dart' as _i8; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart' as _i4; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart' @@ -32,35 +33,31 @@ class MockThreadRepository extends _i1.Mock implements _i2.ThreadRepository { } @override - _i3.Stream<_i4.EmailResponse> getAllEmail(_i5.AccountId? accountId, - {int? position, - _i6.UnsignedInt? limit, + _i3.Stream<_i4.EmailsResponse> getAllEmail(_i5.AccountId? accountId, + {_i6.UnsignedInt? limit, Set<_i7.Comparator>? sort, - _i8.Filter? filter, + _i8.EmailFilter? emailFilter, _i9.Properties? propertiesCreated, - _i9.Properties? propertiesUpdated, - _i10.MailboxId? inMailboxId}) => + _i9.Properties? propertiesUpdated}) => (super.noSuchMethod( Invocation.method(#getAllEmail, [ accountId ], { - #position: position, #limit: limit, #sort: sort, - #filter: filter, + #emailFilter: emailFilter, #propertiesCreated: propertiesCreated, - #propertiesUpdated: propertiesUpdated, - #inMailboxId: inMailboxId + #propertiesUpdated: propertiesUpdated }), - returnValue: Stream<_i4.EmailResponse>.empty()) - as _i3.Stream<_i4.EmailResponse>); + returnValue: Stream<_i4.EmailsResponse>.empty()) + as _i3.Stream<_i4.EmailsResponse>); @override - _i3.Stream<_i4.EmailResponse> refreshChanges( - _i5.AccountId? accountId, _i11.State? currentState, + _i3.Stream<_i4.EmailsResponse> refreshChanges( + _i5.AccountId? accountId, _i10.State? currentState, {Set<_i7.Comparator>? sort, _i9.Properties? propertiesCreated, _i9.Properties? propertiesUpdated, - _i10.MailboxId? inMailboxId}) => + _i11.MailboxId? inMailboxId}) => (super.noSuchMethod( Invocation.method(#refreshChanges, [ accountId, @@ -71,6 +68,23 @@ class MockThreadRepository extends _i1.Mock implements _i2.ThreadRepository { #propertiesUpdated: propertiesUpdated, #inMailboxId: inMailboxId }), - returnValue: Stream<_i4.EmailResponse>.empty()) - as _i3.Stream<_i4.EmailResponse>); + returnValue: Stream<_i4.EmailsResponse>.empty()) + as _i3.Stream<_i4.EmailsResponse>); + @override + _i3.Stream<_i4.EmailsResponse> loadMoreEmails(_i5.AccountId? accountId, + {_i6.UnsignedInt? limit, + Set<_i7.Comparator>? sort, + _i12.Filter? filter, + _i9.Properties? properties}) => + (super.noSuchMethod( + Invocation.method(#loadMoreEmails, [ + accountId + ], { + #limit: limit, + #sort: sort, + #filter: filter, + #properties: properties + }), + returnValue: Stream<_i4.EmailsResponse>.empty()) + as _i3.Stream<_i4.EmailsResponse>); } diff --git a/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.dart b/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.dart index 8a3d1a8b3..2f890291c 100644 --- a/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.dart +++ b/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:core/core.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:model/model.dart'; @@ -41,7 +42,7 @@ void main() { propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, inMailboxId: MailboxFixtures.inboxMailbox.id )).thenAnswer((_) => Stream.fromIterable({ - EmailResponse( + EmailsResponse( emailList: { EmailFixtures.email1, EmailFixtures.email2, @@ -62,8 +63,9 @@ void main() { final states = await streamStates.toList(); - expect(states.length, equals(1)); + expect(states.length, equals(2)); expect(states, containsAllInOrder({ + Right(RefreshingState()), Right(GetAllEmailSuccess( emailList: { EmailFixtures.email1.toPresentationEmail(), diff --git a/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.mocks.dart b/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.mocks.dart index 6dcad9870..ffe7db6df 100644 --- a/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.mocks.dart +++ b/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.mocks.dart @@ -5,13 +5,14 @@ import 'dart:async' as _i3; import 'package:jmap_dart_client/jmap/account_id.dart' as _i5; -import 'package:jmap_dart_client/jmap/core/filter/filter.dart' as _i8; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart' as _i12; import 'package:jmap_dart_client/jmap/core/properties/properties.dart' as _i9; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart' as _i7; -import 'package:jmap_dart_client/jmap/core/state.dart' as _i11; +import 'package:jmap_dart_client/jmap/core/state.dart' as _i10; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart' as _i6; -import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart' as _i10; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart' as _i11; import 'package:mockito/mockito.dart' as _i1; +import 'package:model/email/email_filter.dart' as _i8; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart' as _i4; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart' @@ -32,35 +33,31 @@ class MockThreadRepository extends _i1.Mock implements _i2.ThreadRepository { } @override - _i3.Stream<_i4.EmailResponse> getAllEmail(_i5.AccountId? accountId, - {int? position, - _i6.UnsignedInt? limit, + _i3.Stream<_i4.EmailsResponse> getAllEmail(_i5.AccountId? accountId, + {_i6.UnsignedInt? limit, Set<_i7.Comparator>? sort, - _i8.Filter? filter, + _i8.EmailFilter? emailFilter, _i9.Properties? propertiesCreated, - _i9.Properties? propertiesUpdated, - _i10.MailboxId? inMailboxId}) => + _i9.Properties? propertiesUpdated}) => (super.noSuchMethod( Invocation.method(#getAllEmail, [ accountId ], { - #position: position, #limit: limit, #sort: sort, - #filter: filter, + #emailFilter: emailFilter, #propertiesCreated: propertiesCreated, - #propertiesUpdated: propertiesUpdated, - #inMailboxId: inMailboxId + #propertiesUpdated: propertiesUpdated }), - returnValue: Stream<_i4.EmailResponse>.empty()) - as _i3.Stream<_i4.EmailResponse>); + returnValue: Stream<_i4.EmailsResponse>.empty()) + as _i3.Stream<_i4.EmailsResponse>); @override - _i3.Stream<_i4.EmailResponse> refreshChanges( - _i5.AccountId? accountId, _i11.State? currentState, + _i3.Stream<_i4.EmailsResponse> refreshChanges( + _i5.AccountId? accountId, _i10.State? currentState, {Set<_i7.Comparator>? sort, _i9.Properties? propertiesCreated, _i9.Properties? propertiesUpdated, - _i10.MailboxId? inMailboxId}) => + _i11.MailboxId? inMailboxId}) => (super.noSuchMethod( Invocation.method(#refreshChanges, [ accountId, @@ -71,6 +68,23 @@ class MockThreadRepository extends _i1.Mock implements _i2.ThreadRepository { #propertiesUpdated: propertiesUpdated, #inMailboxId: inMailboxId }), - returnValue: Stream<_i4.EmailResponse>.empty()) - as _i3.Stream<_i4.EmailResponse>); + returnValue: Stream<_i4.EmailsResponse>.empty()) + as _i3.Stream<_i4.EmailsResponse>); + @override + _i3.Stream<_i4.EmailsResponse> loadMoreEmails(_i5.AccountId? accountId, + {_i6.UnsignedInt? limit, + Set<_i7.Comparator>? sort, + _i12.Filter? filter, + _i9.Properties? properties}) => + (super.noSuchMethod( + Invocation.method(#loadMoreEmails, [ + accountId + ], { + #limit: limit, + #sort: sort, + #filter: filter, + #properties: properties + }), + returnValue: Stream<_i4.EmailsResponse>.empty()) + as _i3.Stream<_i4.EmailsResponse>); }