diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index f85eec412..4be84d503 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -138,6 +138,83 @@ class ThreadRepositoryImpl extends ThreadRepository { yield newEmailResponse; } + @override + Stream forceQueryAllEmailsForWeb({ + required Session session, + required AccountId accountId, + UnsignedInt? limit, + int? position, + Set? sort, + EmailFilter? emailFilter, + Properties? propertiesCreated, + }) async* { + final localDataSource = mapDataSource[DataSourceType.local]; + final networkDataSource = mapDataSource[DataSourceType.network]; + + if (localDataSource == null || networkDataSource == null) { + logError( + 'ThreadRepositoryImpl::forceQueryAllEmailsForWeb(): ' + 'Missing required data sources (local or network).', + ); + return; + } + + // Load cached emails + cached state + final cachedList = await localDataSource.getAllEmailCache( + accountId, + session.username, + inMailboxId: emailFilter?.mailboxId, + sort: sort, + limit: limit, + filterOption: emailFilter?.filterOption, + ); + + final cachedState = await stateDataSource.getState( + accountId, + session.username, + StateType.email, + ); + + final localResponse = EmailsResponse( + emailList: cachedList, + state: cachedState, + ); + + log( + 'ThreadRepositoryImpl::forceQueryAllEmailsForWeb(): ' + 'Local cache count = ${cachedList.length}; ' + 'State = ${cachedState?.value}', + ); + + if (localResponse.hasEmails()) { + yield localResponse; + } + + // Query fresh emails from server + final serverResponse = await networkDataSource.getAllEmail( + session, + accountId, + limit: limit, + position: position, + sort: sort, + filter: emailFilter?.filter, + properties: propertiesCreated, + ); + + final serverCount = serverResponse.emailList?.length ?? 0; + + log( + 'ThreadRepositoryImpl::forceQueryAllEmailsForWeb(): ' + 'Server email count = $serverCount', + ); + + // Combine server list + keep existing state + yield EmailsResponse( + emailList: serverResponse.emailList, + state: cachedState, + ); + } + bool _isApproveFilterOption(FilterMessageOption? filterOption, List? listEmailResponse) { return filterOption != FilterMessageOption.all && listEmailResponse!.isNotEmpty; } diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index 686b46c0a..70510b087 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -43,6 +43,16 @@ abstract class ThreadRepository { Properties? propertiesCreated, }); + Stream forceQueryAllEmailsForWeb({ + required Session session, + required AccountId accountId, + UnsignedInt? limit, + int? position, + Set? sort, + EmailFilter? emailFilter, + Properties? propertiesCreated, + }); + Stream refreshChanges( Session session, AccountId accountId, 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 d052fe04a..7d67ed877 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,4 +1,5 @@ -import 'package:core/core.dart'; +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/properties/properties.dart'; @@ -6,11 +7,11 @@ import 'package:jmap_dart_client/jmap/core/session/session.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:model/extensions/email_extension.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_filter.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'; class GetEmailsInMailboxInteractor { final ThreadRepository threadRepository; @@ -28,13 +29,25 @@ class GetEmailsInMailboxInteractor { Properties? propertiesUpdated, bool getLatestChanges = true, bool useCache = true, + bool forceEmailQuery = false, } ) async* { try { yield Right(GetAllEmailLoading()); - if (useCache) { - yield* threadRepository.getAllEmail( + late Stream sourceStream; + + if (forceEmailQuery) { + sourceStream = threadRepository.forceQueryAllEmailsForWeb( + session: session, + accountId: accountId, + limit: limit, + sort: sort, + emailFilter: emailFilter, + propertiesCreated: propertiesCreated, + ); + } else if (useCache) { + sourceStream = threadRepository.getAllEmail( session, accountId, limit: limit, @@ -43,23 +56,24 @@ class GetEmailsInMailboxInteractor { propertiesCreated: propertiesCreated, propertiesUpdated: propertiesUpdated, getLatestChanges: getLatestChanges, - ).map((emailResponse) => _toGetEmailState( - emailResponse: emailResponse, - currentMailboxId: emailFilter?.mailboxId, - )); + ); } else { - yield* threadRepository.loadAllEmailInFolderWithoutCache( + sourceStream = threadRepository.loadAllEmailInFolderWithoutCache( session: session, accountId: accountId, limit: limit, sort: sort, emailFilter: emailFilter, propertiesCreated: propertiesCreated, - ).map((emailResponse) => _toGetEmailState( + ); + } + + yield* sourceStream.map( + (emailResponse) => _toGetEmailState( emailResponse: emailResponse, currentMailboxId: emailFilter?.mailboxId, - )); - } + ), + ); } catch (e) { yield Left(GetAllEmailFailure(e)); } diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 6e76c021e..eaaeb76f6 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -74,6 +74,7 @@ import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; +import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:universal_html/html.dart' as html; typedef StartRangeSelection = int; @@ -273,6 +274,7 @@ class ThreadController extends BaseController with EmailActionController { resetToOriginalValue(); getAllEmailAction( getLatestChanges: mailboxDashBoardController.isFirstSessionLoad, + forceEmailQuery: forceEmailQuery, ); mailboxDashBoardController.setIsFirstSessionLoad(false); } else if (mailbox == null) { // disable current mailbox when search active @@ -353,7 +355,7 @@ class ThreadController extends BaseController with EmailActionController { if (action is RefreshChangeEmailAction) { _refreshEmailChanges(newState: action.newState); } else if (action is RefreshAllEmailAction) { - refreshAllEmail(); + refreshAllEmail(forceEmailQuery: forceEmailQuery); mailboxDashBoardController.clearEmailUIAction(); } }); @@ -472,6 +474,9 @@ class ThreadController extends BaseController with EmailActionController { handleLoadMoreEmailsRequest(); } + bool get forceEmailQuery => + PlatformInfo.isWeb && AppConfig.isForceEmailQueryEnabled; + void _handleErrorGetAllOrRefreshChangesEmail(Object error, StackTrace stackTrace) async { logWarning('ThreadController::_handleErrorGetAllOrRefreshChangesEmail():Error: $error'); if (error is CannotCalculateChangesMethodResponseException) { @@ -578,6 +583,7 @@ class ThreadController extends BaseController with EmailActionController { void getAllEmailAction({ bool getLatestChanges = true, + bool forceEmailQuery = false, }) { log('ThreadController::_getAllEmailAction:getLatestChanges = $getLatestChanges'); if (_session != null &&_accountId != null) { @@ -594,13 +600,14 @@ class ThreadController extends BaseController with EmailActionController { ), getLatestChanges: getLatestChanges, useCache: selectedMailbox?.isCacheable ?? false, + forceEmailQuery: forceEmailQuery, )); } else { consumeState(Stream.value(Left(GetAllEmailFailure(NotFoundSessionException())))); } } - void refreshAllEmail() { + void refreshAllEmail({bool forceEmailQuery = false}) { if (searchController.isSearchEmailRunning) { consumeState(Stream.value(Right(SearchingState()))); } else { @@ -614,7 +621,7 @@ class ThreadController extends BaseController with EmailActionController { if (searchController.isSearchEmailRunning) { _searchEmail(limit: limitEmailFetched); } else { - getAllEmailAction(); + getAllEmailAction(forceEmailQuery: forceEmailQuery); } }