diff --git a/lib/features/cleanup/data/local/recent_search_cache_manager.dart b/lib/features/cleanup/data/local/recent_search_cache_manager.dart index 85abafc09..17cda7de0 100644 --- a/lib/features/cleanup/data/local/recent_search_cache_manager.dart +++ b/lib/features/cleanup/data/local/recent_search_cache_manager.dart @@ -1,10 +1,18 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/extensions/account_id_extensions.dart'; import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/cleanup/domain/model/recent_search_cleanup_rule.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/recent_search_cache.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/extensions/list_recent_search_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; class RecentSearchCacheManager { + static const int _defaultRecentSearchLimit = 10; + final RecentSearchCacheClient _recentSearchCacheClient; RecentSearchCacheManager(this._recentSearchCacheClient); @@ -21,4 +29,61 @@ class RecentSearchCacheManager { await _recentSearchCacheClient.deleteMultipleItem(newListKeyRecent); } } + + Future saveRecentSearch( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ) async { + final keyCache = recentSearch.generateTupleKey(accountId, userName); + final exist = await _recentSearchCacheClient.isExistItem(keyCache); + + if (exist) { + await _recentSearchCacheClient.updateItem( + keyCache, + recentSearch.toRecentSearchCache(), + ); + } else { + await _recentSearchCacheClient.insertItem( + keyCache, + recentSearch.toRecentSearchCache(), + ); + } + } + + Future> getAllLatest( + AccountId accountId, + UserName userName, + { + int? limit, + String? pattern, + } + ) async { + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final listRecentSearchCache = await _recentSearchCacheClient + .getListByNestedKey(nestedKey); + + final listRecentSearch = listRecentSearchCache + .where((recentCache) => _filterRecentSearchCache(recentCache, pattern)) + .map((recentCache) => recentCache.toRecentSearch()) + .toList(); + + listRecentSearch.sortByCreationDate(); + + final newLimit = limit ?? _defaultRecentSearchLimit; + + final newListRecentSearch = listRecentSearch.length > newLimit + ? listRecentSearch.sublist(0, newLimit) + : listRecentSearch; + + return newListRecentSearch; + } + + bool _filterRecentSearchCache(RecentSearchCache recentSearchCache, String? pattern) { + if (pattern == null || pattern.trim().isNotEmpty) { + return true; + } else { + return recentSearchCache.match(pattern); + } + } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/datasource/search_datasource.dart b/lib/features/mailbox_dashboard/data/datasource/search_datasource.dart index 3d5c95d20..572905a5c 100644 --- a/lib/features/mailbox_dashboard/data/datasource/search_datasource.dart +++ b/lib/features/mailbox_dashboard/data/datasource/search_datasource.dart @@ -1,7 +1,20 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; abstract class SearchDataSource { - Future saveRecentSearch(RecentSearch recentSearch); + Future saveRecentSearch( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ); - Future> getAllRecentSearchLatest({int? limit, String? pattern}); + Future> getAllRecentSearchLatest( + AccountId accountId, + UserName userName, + { + int? limit, + String? pattern, + } + ); } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/datasource_impl/search_datasource_impl.dart b/lib/features/mailbox_dashboard/data/datasource_impl/search_datasource_impl.dart index 1c7a6a9c5..ea5373be4 100644 --- a/lib/features/mailbox_dashboard/data/datasource_impl/search_datasource_impl.dart +++ b/lib/features/mailbox_dashboard/data/datasource_impl/search_datasource_impl.dart @@ -1,58 +1,49 @@ -import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_client.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:tmail_ui_user/features/cleanup/data/local/recent_search_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/search_datasource.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/recent_search_cache.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/extensions/list_recent_search_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class SearchDataSourceImpl extends SearchDataSource { - final RecentSearchCacheClient _recentSearchCacheClient; + final RecentSearchCacheManager _recentSearchCacheManager; final ExceptionThrower _exceptionThrower; - SearchDataSourceImpl(this._recentSearchCacheClient, this._exceptionThrower); + SearchDataSourceImpl(this._recentSearchCacheManager, this._exceptionThrower); @override - Future saveRecentSearch(RecentSearch recentSearch) { + Future saveRecentSearch( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ) { return Future.sync(() async { - if (await _recentSearchCacheClient.isExistItem(recentSearch.value)) { - await _recentSearchCacheClient.updateItem( - recentSearch.value, - recentSearch.toRecentSearchCache()); - } else { - await _recentSearchCacheClient.insertItem( - recentSearch.value, - recentSearch.toRecentSearchCache()); - } + return await _recentSearchCacheManager.saveRecentSearch( + accountId, + userName, + recentSearch, + ); }).catchError(_exceptionThrower.throwException); } @override - Future> getAllRecentSearchLatest({int? limit, String? pattern}) { - return Future.sync(() async { - final listRecentSearchCache = await _recentSearchCacheClient.getAll(); - final listRecentSearch = listRecentSearchCache - .where((recentCache) => _filterRecentSearchCache(recentCache, pattern)) - .map((recentCache) => recentCache.toRecentSearch()) - .toList(); - listRecentSearch.sortByCreationDate(); - - final newLimit = limit ?? 10; - - final newListRecentSearch = listRecentSearch.length > newLimit - ? listRecentSearch.sublist(0, newLimit) - : listRecentSearch; - - return newListRecentSearch; - }).catchError(_exceptionThrower.throwException); - } - - bool _filterRecentSearchCache(RecentSearchCache recentSearchCache, String? pattern) { - if (pattern == null || pattern.trim().isEmpty) { - return true; - } else { - return recentSearchCache.match(pattern); + Future> getAllRecentSearchLatest( + AccountId accountId, + UserName userName, + { + int? limit, + String? pattern, } + ) { + return Future.sync(() async { + return await _recentSearchCacheManager.getAllLatest( + accountId, + userName, + limit: limit, + pattern: pattern, + ); + }).catchError(_exceptionThrower.throwException); } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/repository/search_repository_impl.dart b/lib/features/mailbox_dashboard/data/repository/search_repository_impl.dart index e21240964..f07de33a9 100644 --- a/lib/features/mailbox_dashboard/data/repository/search_repository_impl.dart +++ b/lib/features/mailbox_dashboard/data/repository/search_repository_impl.dart @@ -1,4 +1,6 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/search_datasource.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/search_repository.dart'; @@ -10,12 +12,28 @@ class SearchRepositoryImpl extends SearchRepository { SearchRepositoryImpl(this.searchDataSource); @override - Future saveRecentSearch(RecentSearch recentSearch) { - return searchDataSource.saveRecentSearch(recentSearch); + Future saveRecentSearch( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ) { + return searchDataSource.saveRecentSearch(accountId, userName, recentSearch); } @override - Future> getAllRecentSearchLatest({int? limit, String? pattern}) { - return searchDataSource.getAllRecentSearchLatest(limit: limit, pattern: pattern); + Future> getAllRecentSearchLatest( + AccountId accountId, + UserName userName, + { + int? limit, + String? pattern, + } + ) { + return searchDataSource.getAllRecentSearchLatest( + accountId, + userName, + limit: limit, + pattern: pattern, + ); } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/model/recent_search.dart b/lib/features/mailbox_dashboard/domain/model/recent_search.dart index 02f1d7f87..c5a9dc8ed 100644 --- a/lib/features/mailbox_dashboard/domain/model/recent_search.dart +++ b/lib/features/mailbox_dashboard/domain/model/recent_search.dart @@ -1,5 +1,9 @@ import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/extensions/account_id_extensions.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; class RecentSearch with EquatableMixin { final String value; @@ -11,6 +15,10 @@ class RecentSearch with EquatableMixin { return RecentSearch(word, DateTime.now()); } + String generateTupleKey(AccountId accountId, UserName userName) { + return TupleKey(value, accountId.asString, userName.value).encodeKey; + } + @override List get props => [value, creationDate]; } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/repository/search_repository.dart b/lib/features/mailbox_dashboard/domain/repository/search_repository.dart index 1f1fec86e..6c88fb7b1 100644 --- a/lib/features/mailbox_dashboard/domain/repository/search_repository.dart +++ b/lib/features/mailbox_dashboard/domain/repository/search_repository.dart @@ -1,8 +1,21 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; abstract class SearchRepository { - Future saveRecentSearch(RecentSearch recentSearch); + Future saveRecentSearch( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ); - Future> getAllRecentSearchLatest({int? limit, String? pattern}); + Future> getAllRecentSearchLatest( + AccountId accountId, + UserName userName, + { + int? limit, + String? pattern, + } + ); } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/usecases/get_all_recent_search_latest_interactor.dart b/lib/features/mailbox_dashboard/domain/usecases/get_all_recent_search_latest_interactor.dart index a68059732..b9cddef63 100644 --- a/lib/features/mailbox_dashboard/domain/usecases/get_all_recent_search_latest_interactor.dart +++ b/lib/features/mailbox_dashboard/domain/usecases/get_all_recent_search_latest_interactor.dart @@ -1,7 +1,11 @@ import 'dart:core'; -import 'package:core/core.dart'; +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/user_name.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/search_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_all_recent_search_latest_state.dart'; @@ -10,9 +14,21 @@ class GetAllRecentSearchLatestInteractor { GetAllRecentSearchLatestInteractor(this.searchRepository); - Future> execute({int? limit, String? pattern}) async { + Future> execute( + AccountId accountId, + UserName userName, + { + int? limit, + String? pattern, + } + ) async { try { - final listRecent = await searchRepository.getAllRecentSearchLatest(limit: limit, pattern: pattern); + final listRecent = await searchRepository.getAllRecentSearchLatest( + accountId, + userName, + limit: limit, + pattern: pattern, + ); log('GetAllRecentSearchLatestInteractor::execute(): listRecent: ${listRecent.length}'); return Right(GetAllRecentSearchLatestSuccess(listRecent)); } catch (exception) { diff --git a/lib/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart b/lib/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart index 2147c39fd..92ef1aada 100644 --- a/lib/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart +++ b/lib/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart @@ -2,6 +2,8 @@ import 'dart:core'; 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/user_name.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/search_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/save_recent_search_state.dart'; @@ -11,9 +13,17 @@ class SaveRecentSearchInteractor { SaveRecentSearchInteractor(this.searchRepository); - Stream> execute(RecentSearch recentSearch) async* { + Stream> execute( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ) async* { try { - await searchRepository.saveRecentSearch(recentSearch); + await searchRepository.saveRecentSearch( + accountId, + userName, + recentSearch, + ); yield Right(SaveRecentSearchSuccess()); } catch (exception) { yield Left(SaveRecentSearchFailure(exception)); diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 8401484e9..37fd0cfec 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -8,9 +8,9 @@ import 'package:core/utils/print_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; -import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_client.dart'; import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/caching/utils/session_storage_manager.dart'; +import 'package:tmail_ui_user/features/cleanup/data/local/recent_search_cache_manager.dart'; import 'package:tmail_ui_user/features/composer/data/repository/contact_repository_impl.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; @@ -233,7 +233,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find())); Get.lazyPut(() => SearchDataSourceImpl( - Get.find(), + Get.find(), Get.find())); Get.lazyPut(() => ThreadDataSourceImpl( Get.find(), diff --git a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart index 2a5057431..3204cf829 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart @@ -9,6 +9,7 @@ import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; 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/core/user_name.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; @@ -245,13 +246,25 @@ class SearchController extends BaseController with DateRangePickerMixin { searchInputController.text = value; } - void saveRecentSearch(RecentSearch recentSearch) { - consumeState(_saveRecentSearchInteractor.execute(recentSearch)); + void saveRecentSearch( + AccountId accountId, + UserName userName, + RecentSearch recentSearch, + ) { + consumeState(_saveRecentSearchInteractor.execute( + accountId, + userName, + recentSearch, + )); } - Future> getAllRecentSearchAction(String pattern) async { + Future> getAllRecentSearchAction( + AccountId accountId, + UserName userName, + String pattern, + ) async { return await _getAllRecentSearchLatestInteractor - .execute(pattern: pattern) + .execute(accountId, userName, pattern: pattern) .then((result) => result.fold( (failure) => [], (success) => success is GetAllRecentSearchLatestSuccess diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart index b097a2585..2325a53c1 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart @@ -119,7 +119,21 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { padding: const EdgeInsets.only(bottom: 16), child: loadingWidget ), - fetchRecentActionCallback: _searchController.getAllRecentSearchAction, + fetchRecentActionCallback: (pattern) { + final accountId = _dashBoardController.accountId.value; + final userName = _dashBoardController.sessionCurrent?.username; + + if (accountId == null || userName == null) { + logError('SearchInputFormWidget::fetchRecentActionCallback: accountId or userName is null'); + return []; + } else { + return _searchController.getAllRecentSearchAction( + accountId, + userName, + pattern, + ); + } + }, itemRecentBuilder: (context, recent) => RecentSearchItemTileWidget(recent), onRecentSelected: _invokeSelectRecentItem, suggestionsCallback: _dashBoardController.quickSearchEmails, @@ -143,7 +157,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { _searchController.enableSearch(); if (queryString.isNotEmpty) { - _searchController.saveRecentSearch(RecentSearch.now(queryString)); + _saveRecentSearch(queryString); } if (queryString.isNotEmpty || _searchController.listFilterOnSuggestionForm.isNotEmpty) { @@ -157,6 +171,22 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { } } + void _saveRecentSearch(String queryString) { + final accountId = _dashBoardController.accountId.value; + final userName = _dashBoardController.sessionCurrent?.username; + + if (accountId == null || userName == null) { + logError('SearchInputFormWidget::_saveRecentSearch: accountId or userName is null'); + return; + } + + _searchController.saveRecentSearch( + accountId, + userName, + RecentSearch.now(queryString), + ); + } + void _invokeSelectSuggestionItem(PresentationEmail presentationEmail) { _dashBoardController.dispatchAction( OpenEmailDetailedFromSuggestionQuickSearchAction( diff --git a/lib/features/search/email/presentation/search_email_controller.dart b/lib/features/search/email/presentation/search_email_controller.dart index 3481428fe..141268c35 100644 --- a/lib/features/search/email/presentation/search_email_controller.dart +++ b/lib/features/search/email/presentation/search_email_controller.dart @@ -378,8 +378,16 @@ class SearchEmailController extends BaseController } Future> getAllRecentSearchAction({String pattern = ''}) async { + final accountId = mailboxDashBoardController.accountId.value; + final userName = mailboxDashBoardController.sessionCurrent?.username; + + if (accountId == null || userName == null) { + logError('SearchEmailController::getAllRecentSearchAction: accountId or userName is null'); + return []; + } + return _getAllRecentSearchLatestInteractor - .execute(pattern: pattern) + .execute(accountId, userName, pattern: pattern) .then((result) => result.fold( (failure) => [], (success) => success is GetAllRecentSearchLatestSuccess @@ -406,8 +414,20 @@ class SearchEmailController extends BaseController : [])); } - void saveRecentSearch(RecentSearch recentSearch) { - consumeState(_saveRecentSearchInteractor.execute(recentSearch)); + void saveRecentSearch(String queryString) { + final accountId = mailboxDashBoardController.accountId.value; + final userName = mailboxDashBoardController.sessionCurrent?.username; + + if (accountId == null || userName == null) { + logError('SearchEmailController::_saveRecentSearch: accountId or userName is null'); + return; + } + + consumeState(_saveRecentSearchInteractor.execute( + accountId, + userName, + RecentSearch.now(queryString), + )); } void _searchEmailAction(BuildContext context) { @@ -770,7 +790,7 @@ class SearchEmailController extends BaseController void onTextSearchSubmitted(BuildContext context, String text) { final queryString = text.trim(); if (queryString.isNotEmpty) { - saveRecentSearch(RecentSearch.now(queryString)); + saveRecentSearch(queryString); } _searchEmailByQueryString(context: context, queryString: queryString); } diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index a5bcf8c25..509928ad7 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -487,7 +487,7 @@ class SearchEmailView extends GetWidget onTap: () { final query = textSearch.trim(); if (query.isNotEmpty) { - controller.saveRecentSearch(RecentSearch.now(query)); + controller.saveRecentSearch(query); controller.showAllResultSearchAction(context, query); } },