TF-3924 Thread Detail Delay selected email content render

This commit is contained in:
DatDang
2025-08-01 14:39:09 +07:00
committed by Dat H. Pham
parent bfd58d5e91
commit e5e1771834
9 changed files with 138 additions and 66 deletions
@@ -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/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/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/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/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/focus_thread_detail_expanded_email.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.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; emailUnsubscribe.value = null;
} }
} }
_loadThreadOnGetEmailContentSuccess();
} }
void _getEmailContentSuccess(GetEmailContentSuccess success) { void _getEmailContentSuccess(GetEmailContentSuccess success) {
@@ -761,20 +759,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) { if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) {
_jumpScrollViewToTopOfEmail(); _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() { void _jumpScrollViewToTopOfEmail() {
@@ -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/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_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/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/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/extension/toggle_thread_detail_collape_expand.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.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]!, loadMoreCount: loadMoreSegments[indexOfEmailId]!,
), ),
imagePaths: imagePaths, imagePaths: imagePaths,
isLoading: viewState.value.fold( loadingIndex: indexOfEmailId,
(failure) => false,
(success) => success is GettingEmailsByIds &&
success.loadingIndex == indexOfEmailId,
),
); );
} }
@@ -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/email/presentation/bindings/email_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.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: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/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'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
extension HandleGetEmailsByIdsSuccess on ThreadDetailController { extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
void handleGetEmailsByIdsSuccess(GetEmailsByIdsSuccess success) { Future<void> handleGetEmailsByIdsSuccess(GetEmailsByIdsSuccess success) async {
final currentRoute = mailboxDashBoardController.dashboardRoute.value; final currentRoute = mailboxDashBoardController.dashboardRoute.value;
if (currentRoute != DashboardRoutes.threadDetailed) { if (currentRoute != DashboardRoutes.threadDetailed) {
return; 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; final selectedEmailId = mailboxDashBoardController.selectedEmail.value?.id;
if (selectedEmailId == null) return;
for (var presentationEmail in success.presentationEmails) { for (var presentationEmail in success.presentationEmails) {
if (presentationEmail.id == null) continue; if (presentationEmail.id == null) continue;
@@ -24,16 +49,17 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
continue; continue;
} }
if (presentationEmail.id == selectedEmailId) {
EmailBindings(currentEmailId: presentationEmail.id).dependencies();
currentExpandedEmailId.value = presentationEmail.id;
}
emailIdsPresentation[presentationEmail.id!] = presentationEmail.copyWith( emailIdsPresentation[presentationEmail.id!] = presentationEmail.copyWith(
emailInThreadStatus: presentationEmail.id == selectedEmailId emailInThreadStatus: EmailInThreadStatus.collapsed,
? EmailInThreadStatus.expanded
: 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();
}
} }
} }
@@ -34,7 +34,15 @@ extension InitializeThreadDetailEmails on ThreadDetailController {
)))); ))));
return; 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( consumeState(getEmailsByIdsInteractor.execute(
session!, session!,
@@ -21,6 +21,7 @@ extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController {
return; return;
} }
emailIdsPresentation.clear();
scrollController ??= ScrollController(); scrollController ??= ScrollController();
if (currentExpandedEmailId.value == null) { if (currentExpandedEmailId.value == null) {
@@ -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/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/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_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 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import '../../manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; import '../../manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart';
@@ -109,9 +110,16 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
} }
final nonPageViewThread = Expanded( final nonPageViewThread = Expanded(
child: SingleChildScrollView( child: Stack(
controller: controller.scrollController, children: [
child: threadBody, SingleChildScrollView(
controller: controller.scrollController,
child: threadBody,
),
ThreadDetailCupertinoLoadingWidget(
threadDetailController: controller,
),
],
), ),
); );
@@ -122,18 +130,25 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
if (currentIndex == -1) return nonPageViewThread; if (currentIndex == -1) return nonPageViewThread;
return Expanded( return Expanded(
child: PageView.builder( child: Stack(
controller: manager.pageController, children: [
itemCount: manager.isThreadDetailEnabled PageView.builder(
? manager.availableThreadIds.length controller: manager.pageController,
: manager.currentDisplayedEmails.length, itemCount: manager.isThreadDetailEnabled
itemBuilder: (context, index) { ? manager.availableThreadIds.length
if (index != currentIndex) { : manager.currentDisplayedEmails.length,
return const SizedBox.shrink(); itemBuilder: (context, index) {
} if (index != currentIndex) {
return SingleChildScrollView(child: threadBody); return const SizedBox.shrink();
}, }
onPageChanged: controller.onThreadPageChanged, return SingleChildScrollView(child: threadBody);
},
onPageChanged: controller.onThreadPageChanged,
),
ThreadDetailCupertinoLoadingWidget(
threadDetailController: controller,
),
],
), ),
); );
} }
@@ -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(),
);
});
}
}
@@ -3,6 +3,10 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart'; import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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 { class ThreadDetailLoadMoreCircle extends StatefulWidget {
const ThreadDetailLoadMoreCircle({ const ThreadDetailLoadMoreCircle({
@@ -10,13 +14,13 @@ class ThreadDetailLoadMoreCircle extends StatefulWidget {
required this.count, required this.count,
required this.onTap, required this.onTap,
required this.imagePaths, required this.imagePaths,
required this.isLoading, required this.loadingIndex,
}); });
final int count; final int count;
final VoidCallback onTap; final VoidCallback onTap;
final ImagePaths imagePaths; final ImagePaths imagePaths;
final bool isLoading; final int loadingIndex;
@override @override
State<ThreadDetailLoadMoreCircle> createState() => _ThreadDetailLoadMoreCircleState(); State<ThreadDetailLoadMoreCircle> createState() => _ThreadDetailLoadMoreCircleState();
@@ -24,6 +28,7 @@ class ThreadDetailLoadMoreCircle extends StatefulWidget {
class _ThreadDetailLoadMoreCircleState extends State<ThreadDetailLoadMoreCircle> { class _ThreadDetailLoadMoreCircleState extends State<ThreadDetailLoadMoreCircle> {
final _isHover = ValueNotifier(false); final _isHover = ValueNotifier(false);
final threadDetailController = Get.find<ThreadDetailController>();
@override @override
void dispose() { void dispose() {
@@ -67,26 +72,34 @@ class _ThreadDetailLoadMoreCircleState extends State<ThreadDetailLoadMoreCircle>
child: ValueListenableBuilder( child: ValueListenableBuilder(
valueListenable: _isHover, valueListenable: _isHover,
builder: (context, isHover, child) { builder: (context, isHover, child) {
if (widget.isLoading) { return Obx(() {
return const SizedBox( final isLoading = threadDetailController.viewState.value.fold(
width: 24, (failure) => false,
height: 24, (success) => success is GettingEmailsByIds &&
child: CupertinoLoadingWidget(), success.loadingIndex == widget.loadingIndex,
); );
}
if (isHover) { if (isLoading) {
return child ?? const SizedBox.shrink(); return const SizedBox(
} width: 24,
height: 24,
child: CupertinoLoadingWidget(),
);
}
return Text( if (isHover) {
'${widget.count}', return child ?? const SizedBox.shrink();
style: Theme.of(context).textTheme.bodyMedium?.copyWith( }
fontWeight: FontWeight.normal,
height: 24 / 16, return Text(
letterSpacing: -0.1, '${widget.count}',
), style: Theme.of(context).textTheme.bodyMedium?.copyWith(
); fontWeight: FontWeight.normal,
height: 24 / 16,
letterSpacing: -0.1,
),
);
});
}, },
child: SvgPicture.asset(widget.imagePaths.icExpandArrows), child: SvgPicture.asset(widget.imagePaths.icExpandArrows),
), ),
@@ -70,6 +70,8 @@ void main() {
threadId: ThreadId(Id('1')), threadId: ThreadId(Id('1')),
); );
when(threadDetailController.currentExpandedEmailId).thenReturn(Rxn()); when(threadDetailController.currentExpandedEmailId).thenReturn(Rxn());
when(threadDetailController.emailIdsPresentation)
.thenReturn(<EmailId, PresentationEmail?>{}.obs);
// act // act
threadDetailController.onSelectedEmailUpdated( threadDetailController.onSelectedEmailUpdated(