diff --git a/core/lib/presentation/views/pull_to_refresh/pull_to_refresh_widget.dart b/core/lib/presentation/views/pull_to_refresh/pull_to_refresh_widget.dart index b3125e2f0..e464d883d 100644 --- a/core/lib/presentation/views/pull_to_refresh/pull_to_refresh_widget.dart +++ b/core/lib/presentation/views/pull_to_refresh/pull_to_refresh_widget.dart @@ -13,11 +13,9 @@ class PullToRefreshWidget extends StatefulWidget { final String normalRefreshText; final String deepRefreshText; final String pullDownToRefreshText; - final String refreshingText; - final String pullingText; final String releaseToText; - final String pullFurtherForText; final String pullHarderForText; + final Icon? pullDownToRefreshIcon; final Icon? normalRefreshIcon; final Icon? deepRefreshIcon; @@ -26,15 +24,13 @@ class PullToRefreshWidget extends StatefulWidget { required this.child, required this.onNormalRefresh, required this.onDeepRefresh, - this.deepRefreshThreshold = 200.0, + this.deepRefreshThreshold = 150.0, this.normalRefreshText = 'Refresh', this.deepRefreshText = 'Deep refresh', this.pullDownToRefreshText = 'Pull down to refresh', - this.refreshingText = 'Refreshing', - this.pullingText = 'Pulling', this.releaseToText = 'Release to', - this.pullFurtherForText = 'Pull further for', this.pullHarderForText = 'Pull harder for', + this.pullDownToRefreshIcon = const Icon(Icons.arrow_downward, size: 20), this.normalRefreshIcon = const Icon(Icons.refresh, size: 20), this.deepRefreshIcon = const Icon(Icons.autorenew, size: 20), }) : super(key: key); @@ -46,23 +42,26 @@ class PullToRefreshWidget extends StatefulWidget { class _PullToRefreshWidgetState extends State { final RefreshController _refreshController = RefreshController(); double _scrollOffset = 0.0; - String? _lastAction; + bool _shouldDeepRefresh = false; + + bool get isReachDeepRefresh => _scrollOffset > widget.deepRefreshThreshold; @override Widget build(BuildContext context) { return NotificationListener( onNotification: (notification) { if (notification is ScrollUpdateNotification) { - final newOffset = notification.metrics.pixels; + final newOffset = notification.metrics.pixels.abs(); final crossedThreshold = - (_scrollOffset.abs() <= widget.deepRefreshThreshold && - newOffset.abs() > widget.deepRefreshThreshold) || - (_scrollOffset.abs() > widget.deepRefreshThreshold && - newOffset.abs() <= widget.deepRefreshThreshold); + (_scrollOffset <= widget.deepRefreshThreshold && newOffset > widget.deepRefreshThreshold) || + (isReachDeepRefresh && newOffset <= widget.deepRefreshThreshold); if (crossedThreshold || _scrollOffset != newOffset) { setState(() { _scrollOffset = newOffset; + if (notification.dragDetails != null) { + _shouldDeepRefresh = newOffset > widget.deepRefreshThreshold; + } }); } } @@ -74,32 +73,24 @@ class _PullToRefreshWidgetState extends State { enablePullUp: false, header: CustomHeader( builder: (context, mode) { - String statusText; + if (mode != RefreshStatus.idle && + mode != RefreshStatus.canRefresh) { + return const SizedBox.shrink(); + } + + String? statusText; Icon? leadingIcon; if (mode == RefreshStatus.idle) { statusText = widget.pullDownToRefreshText; - leadingIcon = widget.normalRefreshIcon; + leadingIcon = widget.pullDownToRefreshIcon; } else if (mode == RefreshStatus.canRefresh) { - if (_scrollOffset.abs() > widget.deepRefreshThreshold) { - statusText = '${widget.releaseToText} ${widget.deepRefreshText}'; - leadingIcon = widget.deepRefreshIcon; - } else { - statusText = '${widget.releaseToText} ${widget.normalRefreshText}'; - leadingIcon = widget.normalRefreshIcon; - } - } else if (mode == RefreshStatus.refreshing || mode == RefreshStatus.completed) { - statusText = '${widget.refreshingText}...'; - leadingIcon = _lastAction == widget.deepRefreshText - ? widget.deepRefreshIcon - : widget.normalRefreshIcon; - } else { - statusText = '${widget.pullingText}...'; - leadingIcon = widget.normalRefreshIcon; + statusText = '${widget.releaseToText} ${isReachDeepRefresh ? widget.deepRefreshText : widget.normalRefreshText}'; + leadingIcon = isReachDeepRefresh ? widget.deepRefreshIcon : widget.normalRefreshIcon; } return SizedBox( - height: clampDouble(_scrollOffset.abs(), 60, widget.deepRefreshThreshold), + height: clampDouble(_scrollOffset, 60, widget.deepRefreshThreshold), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -113,20 +104,18 @@ class _PullToRefreshWidgetState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( - statusText, + statusText ?? '', style: ThemeUtils.textStyleBodyBody1(color: Colors.black), ), ), ), ], ), - if (mode == RefreshStatus.canRefresh) + if (mode == RefreshStatus.canRefresh && !isReachDeepRefresh) Padding( padding: const EdgeInsetsDirectional.only(top: 4, start: 8, end: 8), child: Text( - _scrollOffset.abs() > widget.deepRefreshThreshold - ? '${widget.pullFurtherForText} ${widget.deepRefreshText.toLowerCase()}' - : '${widget.pullHarderForText} ${widget.deepRefreshText.toLowerCase()}', + '${widget.pullHarderForText} ${widget.deepRefreshText.toLowerCase()}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColor.steelGray400, ), @@ -140,14 +129,12 @@ class _PullToRefreshWidgetState extends State { height: widget.deepRefreshThreshold, ), onRefresh: () { - if (_scrollOffset.abs() > widget.deepRefreshThreshold) { - _lastAction = widget.deepRefreshText; + if (_shouldDeepRefresh) { widget.onDeepRefresh(); } else { - _lastAction = widget.normalRefreshText; widget.onNormalRefresh(); } - _refreshController.refreshCompleted(); + _refreshController.refreshToIdle(); }, child: widget.child, ), 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 5d1562fd0..1d93aedeb 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -7,6 +7,7 @@ import 'package:core/utils/preview_eml_file_utils.dart'; 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'; @@ -103,7 +104,6 @@ import 'package:tmail_ui_user/features/offline_mode/manager/new_email_cache_work import 'package:tmail_ui_user/features/offline_mode/manager/opened_email_cache_manager.dart'; import 'package:tmail_ui_user/features/offline_mode/manager/opened_email_cache_worker_queue.dart'; import 'package:tmail_ui_user/features/offline_mode/manager/sending_email_cache_manager.dart'; -import 'package:tmail_ui_user/features/push_notification/data/local/fcm_cache_manager.dart'; import 'package:tmail_ui_user/features/quotas/presentation/quotas_bindings.dart'; import 'package:tmail_ui_user/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart'; import 'package:tmail_ui_user/features/search/email/presentation/search_email_bindings.dart'; @@ -239,9 +239,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find())); Get.lazyPut(() => LocalThreadDataSourceImpl( Get.find(), - Get.find(), - Get.find(), - Get.find(), + Get.find(), Get.find(), )); Get.lazyPut(() => StateDataSourceImpl( 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 0b5944769..309eeef1c 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 @@ -2,8 +2,6 @@ import 'dart:async'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; -import 'package:core/utils/file_utils.dart'; -import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; @@ -16,10 +14,7 @@ import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/presentation_email.dart'; -import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; -import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; -import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; -import 'package:tmail_ui_user/features/push_notification/data/local/fcm_cache_manager.dart'; +import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; @@ -31,16 +26,12 @@ import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class LocalThreadDataSourceImpl extends ThreadDataSource { final EmailCacheManager _emailCacheManager; - final FCMCacheManager _fcmCacheManager; - final StateCacheManager _stateCacheManager; - final FileUtils _fileUtils; + final CachingManager _cachingManager; final ExceptionThrower _exceptionThrower; LocalThreadDataSourceImpl( this._emailCacheManager, - this._fcmCacheManager, - this._stateCacheManager, - this._fileUtils, + this._cachingManager, this._exceptionThrower, ); @@ -144,16 +135,10 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { @override Future clearEmailCacheAndStateCache(AccountId accountId, Session session) { return Future.sync(() async { - return await Future.wait([ - _stateCacheManager.deleteState(accountId, session.username, StateType.email), - _emailCacheManager.clearAll(), - if (PlatformInfo.isMobile) - ...[ - _fileUtils.removeFolder(CachingConstants.newEmailsContentFolderName), - _fileUtils.removeFolder(CachingConstants.openedEmailContentFolderName), - _fcmCacheManager.clearAllEmailState(accountId, session), - ], - ]); + return await _cachingManager.clearAllEmailAndStateCache( + accountId: accountId, + userName: session.username, + ); }).catchError(_exceptionThrower.throwException); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart b/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart index b4fbac0ab..2e0eab58a 100644 --- a/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart +++ b/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart @@ -1,12 +1,13 @@ +import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart'; -import 'package:rich_text_composer/views/commons/logger.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/state/clean_and_get_all_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; @@ -14,6 +15,8 @@ extension HandlePullToRefreshListEmailExtension on ThreadController { Future onRefresh() async { log('HandlePullToRefreshListEmailExtension::onRefresh:Started'); + consumeState(Stream.value(Right(GetAllEmailLoading()))); + await Future.delayed(const Duration(milliseconds: 300)); // Create loading effect canLoadMore = false; loadingMoreStatus.value == LoadingMoreStatus.idle; getAllEmailAction(); @@ -21,8 +24,7 @@ extension HandlePullToRefreshListEmailExtension on ThreadController { Future onCleanAndRefresh() async { log('HandlePullToRefreshListEmailExtension::onCleanAndRefresh:Started'); - canLoadMore = false; - loadingMoreStatus.value == LoadingMoreStatus.idle; + resetToOriginalValue(); cleanAndGetAllEmailAction(); } diff --git a/lib/features/thread/presentation/thread_bindings.dart b/lib/features/thread/presentation/thread_bindings.dart index 8874aef21..760737ab5 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -1,12 +1,11 @@ import 'package:core/data/model/source_type/data_source_type.dart'; -import 'package:core/utils/file_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/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/state_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; -import 'package:tmail_ui_user/features/push_notification/data/local/fcm_cache_manager.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; import 'package:tmail_ui_user/features/thread/data/datasource_impl/local_thread_datasource_impl.dart'; import 'package:tmail_ui_user/features/thread/data/datasource_impl/thread_datasource_impl.dart'; @@ -56,9 +55,7 @@ class ThreadBindings extends BaseBindings { Get.find())); Get.lazyPut(() => LocalThreadDataSourceImpl( Get.find(), - Get.find(), - Get.find(), - Get.find(), + Get.find(), Get.find(), )); Get.lazyPut(() => StateDataSourceImpl( diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index ed1f07a63..74e401e02 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -267,14 +267,14 @@ class ThreadController extends BaseController with EmailActionController { && mailbox.mailboxId != _currentMemoryMailboxId) { _currentMemoryMailboxId = mailbox.id; consumeState(Stream.value(Right(GetAllEmailLoading()))); - _resetToOriginalValue(); + resetToOriginalValue(); getAllEmailAction( getLatestChanges: mailboxDashBoardController.isFirstSessionLoad, ); mailboxDashBoardController.setIsFirstSessionLoad(false); } else if (mailbox == null) { // disable current mailbox when search active _currentMemoryMailboxId = null; - _resetToOriginalValue(); + resetToOriginalValue(); } }); @@ -468,7 +468,7 @@ class ThreadController extends BaseController with EmailActionController { accountId: _accountId, userName: _session?.username, ); - _getAllEmailAction(); + getAllEmailAction(); } else if (error is MethodLevelErrors) { if (currentOverlayContext != null && error.message != null) { appToast.showToastErrorMessage( @@ -480,8 +480,8 @@ class ThreadController extends BaseController with EmailActionController { } } - void _resetToOriginalValue() { - log('ThreadController::_resetToOriginalValue'); + void resetToOriginalValue() { + log('ThreadController::resetToOriginalValue'); mailboxDashBoardController.emailsInCurrentMailbox.clear(); mailboxDashBoardController.listEmailSelected.clear(); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 1fac33572..9ba424bef 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -413,9 +413,6 @@ class ThreadView extends GetWidget pullDownToRefreshText: AppLocalizations.of(context).pullDownToRefresh, normalRefreshText: AppLocalizations.of(context).refresh, deepRefreshText: AppLocalizations.of(context).deepRefresh, - refreshingText: AppLocalizations.of(context).refreshing, - pullingText: AppLocalizations.of(context).pulling, - pullFurtherForText: AppLocalizations.of(context).pullFurtherFor, pullHarderForText: AppLocalizations.of(context).pullHarderFor, child: listView, ); @@ -699,9 +696,6 @@ class ThreadView extends GetWidget pullDownToRefreshText: AppLocalizations.of(context).pullDownToRefresh, normalRefreshText: AppLocalizations.of(context).refresh, deepRefreshText: AppLocalizations.of(context).deepRefresh, - refreshingText: AppLocalizations.of(context).refreshing, - pullingText: AppLocalizations.of(context).pulling, - pullFurtherForText: AppLocalizations.of(context).pullFurtherFor, pullHarderForText: AppLocalizations.of(context).pullHarderFor, child: EmptyEmailsWidget( key: const Key('empty_thread_view'), diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 2c7f0e81f..9de2beea1 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-04-03T14:19:11.171517", + "@@last_modified": "2025-04-14T04:16:15.237055", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -2968,12 +2968,6 @@ "placeholders_order": [], "placeholders": {} }, - "errorWhileFetchingSubaddress": "Error while fetching the subaddress", - "@errorWhileFetchingSubaddress": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "connectedToTheInternet": "Connected to the internet", "@connectedToTheInternet": { "type": "text", @@ -4424,30 +4418,12 @@ "placeholders_order": [], "placeholders": {} }, - "refreshing": "Refreshing", - "@refreshing": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "pulling": "Pulling", - "@pulling": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "releaseTo": "Release to", "@releaseTo": { "type": "text", "placeholders_order": [], "placeholders": {} }, - "pullFurtherFor": "Pull further for", - "@pullFurtherFor": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "pullHarderFor": "Pull harder for", "@pullHarderFor": { "type": "text", diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 10dbf1733..55089d8f8 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4647,20 +4647,6 @@ class AppLocalizations { ); } - String get refreshing { - return Intl.message( - 'Refreshing', - name: 'refreshing', - ); - } - - String get pulling { - return Intl.message( - 'Pulling', - name: 'pulling', - ); - } - String get releaseTo { return Intl.message( 'Release to', @@ -4668,13 +4654,6 @@ class AppLocalizations { ); } - String get pullFurtherFor { - return Intl.message( - 'Pull further for', - name: 'pullFurtherFor', - ); - } - String get pullHarderFor { return Intl.message( 'Pull harder for',