From e5e1771834424cd69b6a7c0e7176ce95e45531d5 Mon Sep 17 00:00:00 2001 From: DatDang Date: Fri, 1 Aug 2025 14:39:09 +0700 Subject: [PATCH] TF-3924 Thread Detail Delay selected email content render --- .../controller/single_email_controller.dart | 16 ------ .../get_thread_details_email_views.dart | 7 +-- .../handle_get_emails_by_ids_success.dart | 44 ++++++++++++---- .../initialize_thread_detail_emails.dart | 10 +++- ...read_detail_on_selected_email_updated.dart | 1 + .../presentation/thread_detail_view.dart | 45 ++++++++++------ ...hread_detail_cupertino_loading_widget.dart | 28 ++++++++++ .../thread_detail_load_more_circle.dart | 51 ++++++++++++------- ...detail_on_selected_email_updated_test.dart | 2 + 9 files changed, 138 insertions(+), 66 deletions(-) create mode 100644 lib/features/thread_detail/presentation/widgets/thread_detail_cupertino_loading_widget.dart diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 2ad22a584..8dbcfd481 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -124,7 +124,6 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart'; -import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart'; @@ -690,7 +689,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { emailUnsubscribe.value = null; } } - _loadThreadOnGetEmailContentSuccess(); } void _getEmailContentSuccess(GetEmailContentSuccess success) { @@ -761,20 +759,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) { _jumpScrollViewToTopOfEmail(); } - _loadThreadOnGetEmailContentSuccess(); - } - - void _loadThreadOnGetEmailContentSuccess() { - if (currentEmail?.threadId != null && - currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id && - _threadDetailController?.loadThreadOnThreadChanged == true) { - _threadDetailController?.loadThreadOnThreadChanged = false; - mailboxDashBoardController.dispatchThreadDetailUIAction( - LoadThreadDetailAfterSelectedEmailAction( - currentEmail!.threadId!, - ) - ); - } } void _jumpScrollViewToTopOfEmail() { diff --git a/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart b/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart index c32c3f69b..2957f7905 100644 --- a/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart +++ b/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart @@ -5,7 +5,6 @@ import 'package:model/email/email_in_thread_status.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_open_email_address_detail_action.dart'; -import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_load_more_segments.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/toggle_thread_detail_collape_expand.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; @@ -32,11 +31,7 @@ extension GetThreadDetailEmailViews on ThreadDetailController { loadMoreCount: loadMoreSegments[indexOfEmailId]!, ), imagePaths: imagePaths, - isLoading: viewState.value.fold( - (failure) => false, - (success) => success is GettingEmailsByIds && - success.loadingIndex == indexOfEmailId, - ), + loadingIndex: indexOfEmailId, ); } diff --git a/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart b/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart index 7a72eed14..4cea6ca75 100644 --- a/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart +++ b/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart @@ -1,17 +1,42 @@ +import 'package:core/utils/platform_info.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:model/email/email_in_thread_status.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; extension HandleGetEmailsByIdsSuccess on ThreadDetailController { - void handleGetEmailsByIdsSuccess(GetEmailsByIdsSuccess success) { + Future handleGetEmailsByIdsSuccess(GetEmailsByIdsSuccess success) async { final currentRoute = mailboxDashBoardController.dashboardRoute.value; if (currentRoute != DashboardRoutes.threadDetailed) { return; } + if (success is PreloadEmailsByIdsSuccess && + success.presentationEmails.isNotEmpty) { + final email = success.presentationEmails.first; + final emailId = email.id; + if (emailId == null) return; + EmailBindings(currentEmailId: emailId).dependencies(); + currentExpandedEmailId.value = emailId; + final isInternetConnected = await networkConnectionController.hasInternetConnection(); + + if (isThreadDetailEnabled && isInternetConnected) { + loadThreadOnThreadChanged = false; + mailboxDashBoardController.dispatchThreadDetailUIAction( + LoadThreadDetailAfterSelectedEmailAction(email.threadId!), + ); + } else { + emailIdsPresentation[emailId] = emailIdsPresentation[emailId]?.copyWith( + emailInThreadStatus: EmailInThreadStatus.expanded, + ); + } + return; + } + final selectedEmailId = mailboxDashBoardController.selectedEmail.value?.id; + if (selectedEmailId == null) return; for (var presentationEmail in success.presentationEmails) { if (presentationEmail.id == null) continue; @@ -24,16 +49,17 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController { continue; } - if (presentationEmail.id == selectedEmailId) { - EmailBindings(currentEmailId: presentationEmail.id).dependencies(); - currentExpandedEmailId.value = presentationEmail.id; - } emailIdsPresentation[presentationEmail.id!] = presentationEmail.copyWith( - emailInThreadStatus: presentationEmail.id == selectedEmailId - ? EmailInThreadStatus.expanded - : EmailInThreadStatus.collapsed, + emailInThreadStatus: EmailInThreadStatus.collapsed, ); } - threadDetailManager.currentMobilePageViewIndex.refresh(); + if (currentExpandedEmailId.value != null) { + emailIdsPresentation[currentExpandedEmailId.value!] = emailIdsPresentation[currentExpandedEmailId.value!]?.copyWith( + emailInThreadStatus: EmailInThreadStatus.expanded, + ); + } + if (PlatformInfo.isMobile) { + threadDetailManager.currentMobilePageViewIndex.refresh(); + } } } \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart b/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart index 7ddcdc630..8675430af 100644 --- a/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart +++ b/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart @@ -34,7 +34,15 @@ extension InitializeThreadDetailEmails on ThreadDetailController { )))); return; } - if (_currentThreadOnlyContainsSelectedEmail(selectedEmailId)) return; + if (_currentThreadOnlyContainsSelectedEmail(selectedEmailId) && + mailboxDashBoardController.selectedEmail.value != null) { + if (!loadThreadOnThreadChanged) { + consumeState(Stream.value(Right(GetEmailsByIdsSuccess( + [mailboxDashBoardController.selectedEmail.value!], + )))); + } + return; + } consumeState(getEmailsByIdsInteractor.execute( session!, diff --git a/lib/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart b/lib/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart index 5871495d3..c7041d32d 100644 --- a/lib/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart +++ b/lib/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart @@ -21,6 +21,7 @@ extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController { return; } + emailIdsPresentation.clear(); scrollController ??= ScrollController(); if (currentExpandedEmailId.value == null) { diff --git a/lib/features/thread_detail/presentation/thread_detail_view.dart b/lib/features/thread_detail/presentation/thread_detail_view.dart index 19ee70a52..d078a0a24 100644 --- a/lib/features/thread_detail/presentation/thread_detail_view.dart +++ b/lib/features/thread_detail/presentation/thread_detail_view.dart @@ -20,6 +20,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_t import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_thread_page_changed.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_app_bar.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_cupertino_loading_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import '../../manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; @@ -109,9 +110,16 @@ class ThreadDetailView extends GetWidget { } final nonPageViewThread = Expanded( - child: SingleChildScrollView( - controller: controller.scrollController, - child: threadBody, + child: Stack( + children: [ + SingleChildScrollView( + controller: controller.scrollController, + child: threadBody, + ), + ThreadDetailCupertinoLoadingWidget( + threadDetailController: controller, + ), + ], ), ); @@ -122,18 +130,25 @@ class ThreadDetailView extends GetWidget { if (currentIndex == -1) return nonPageViewThread; return Expanded( - child: PageView.builder( - controller: manager.pageController, - itemCount: manager.isThreadDetailEnabled - ? manager.availableThreadIds.length - : manager.currentDisplayedEmails.length, - itemBuilder: (context, index) { - if (index != currentIndex) { - return const SizedBox.shrink(); - } - return SingleChildScrollView(child: threadBody); - }, - onPageChanged: controller.onThreadPageChanged, + child: Stack( + children: [ + PageView.builder( + controller: manager.pageController, + itemCount: manager.isThreadDetailEnabled + ? manager.availableThreadIds.length + : manager.currentDisplayedEmails.length, + itemBuilder: (context, index) { + if (index != currentIndex) { + return const SizedBox.shrink(); + } + return SingleChildScrollView(child: threadBody); + }, + onPageChanged: controller.onThreadPageChanged, + ), + ThreadDetailCupertinoLoadingWidget( + threadDetailController: controller, + ), + ], ), ); } diff --git a/lib/features/thread_detail/presentation/widgets/thread_detail_cupertino_loading_widget.dart b/lib/features/thread_detail/presentation/widgets/thread_detail_cupertino_loading_widget.dart new file mode 100644 index 000000000..3abceefe7 --- /dev/null +++ b/lib/features/thread_detail/presentation/widgets/thread_detail_cupertino_loading_widget.dart @@ -0,0 +1,28 @@ +import 'package:core/presentation/views/loading/cupertino_loading_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; + +class ThreadDetailCupertinoLoadingWidget extends StatelessWidget { + const ThreadDetailCupertinoLoadingWidget({ + super.key, + required this.threadDetailController, + }); + + final ThreadDetailController threadDetailController; + + @override + Widget build(BuildContext context) { + return Obx(() { + return threadDetailController.viewState.value.fold( + (failure) => const SizedBox.shrink(), + (success) => + success is GettingThreadById || success is GettingEmailsByIds + ? const Center(child: CupertinoLoadingWidget()) + : const SizedBox.shrink(), + ); + }); + } +} diff --git a/lib/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart b/lib/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart index 78894f36c..5fd2fc700 100644 --- a/lib/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart +++ b/lib/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart @@ -3,6 +3,10 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/loading/cupertino_loading_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/instance_manager.dart'; +import 'package:get/state_manager.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; class ThreadDetailLoadMoreCircle extends StatefulWidget { const ThreadDetailLoadMoreCircle({ @@ -10,13 +14,13 @@ class ThreadDetailLoadMoreCircle extends StatefulWidget { required this.count, required this.onTap, required this.imagePaths, - required this.isLoading, + required this.loadingIndex, }); final int count; final VoidCallback onTap; final ImagePaths imagePaths; - final bool isLoading; + final int loadingIndex; @override State createState() => _ThreadDetailLoadMoreCircleState(); @@ -24,6 +28,7 @@ class ThreadDetailLoadMoreCircle extends StatefulWidget { class _ThreadDetailLoadMoreCircleState extends State { final _isHover = ValueNotifier(false); + final threadDetailController = Get.find(); @override void dispose() { @@ -67,26 +72,34 @@ class _ThreadDetailLoadMoreCircleState extends State child: ValueListenableBuilder( valueListenable: _isHover, builder: (context, isHover, child) { - if (widget.isLoading) { - return const SizedBox( - width: 24, - height: 24, - child: CupertinoLoadingWidget(), + return Obx(() { + final isLoading = threadDetailController.viewState.value.fold( + (failure) => false, + (success) => success is GettingEmailsByIds && + success.loadingIndex == widget.loadingIndex, ); - } - if (isHover) { - return child ?? const SizedBox.shrink(); - } + if (isLoading) { + return const SizedBox( + width: 24, + height: 24, + child: CupertinoLoadingWidget(), + ); + } - return Text( - '${widget.count}', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.normal, - height: 24 / 16, - letterSpacing: -0.1, - ), - ); + if (isHover) { + return child ?? const SizedBox.shrink(); + } + + return Text( + '${widget.count}', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.normal, + height: 24 / 16, + letterSpacing: -0.1, + ), + ); + }); }, child: SvgPicture.asset(widget.imagePaths.icExpandArrows), ), diff --git a/test/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated_test.dart b/test/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated_test.dart index 29acc2f66..584209513 100644 --- a/test/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated_test.dart +++ b/test/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated_test.dart @@ -70,6 +70,8 @@ void main() { threadId: ThreadId(Id('1')), ); when(threadDetailController.currentExpandedEmailId).thenReturn(Rxn()); + when(threadDetailController.emailIdsPresentation) + .thenReturn({}.obs); // act threadDetailController.onSelectedEmailUpdated(