TF-3699 Thread Detail Next previous actions (#3843)

This commit is contained in:
Dat Dang
2025-07-16 17:15:17 +07:00
committed by Dat H. Pham
parent 4d9a44df66
commit 7332b731dc
28 changed files with 581 additions and 241 deletions
@@ -127,6 +127,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_
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';
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/update_cached_list_email_loaded.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
@@ -204,6 +205,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
: _threadDetailController?.emailIdsPresentation[_currentEmailId];
}
bool get isOnlyEmailInThread =>
_threadDetailController?.emailIdsPresentation.length == 1;
bool get calendarEventProcessing => viewState.value.fold(
(failure) => false,
(success) => success is CalendarEventReplying);
@@ -680,14 +684,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
emailUnsubscribe.value = null;
}
}
if (currentEmail?.threadId != null &&
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id) {
mailboxDashBoardController.dispatchThreadDetailUIAction(
LoadThreadDetailAfterSelectedEmailAction(
currentEmail!.threadId!,
)
);
}
_loadThreadOnGetEmailContentSuccess();
}
void _getEmailContentSuccess(GetEmailContentSuccess success) {
@@ -758,9 +755,14 @@ 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 &&
success is! GetEmailContentFromThreadCacheSuccess) {
_threadDetailController?.loadThreadOnThreadChanged == true) {
_threadDetailController?.loadThreadOnThreadChanged = false;
mailboxDashBoardController.dispatchThreadDetailUIAction(
LoadThreadDetailAfterSelectedEmailAction(
currentEmail!.threadId!,
@@ -2391,4 +2393,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
);
}
void onScrollHorizontalEnd(bool isLeftDirection) {
if (isLeftDirection) {
_threadDetailController?.onPreviousMobile();
} else {
_threadDetailController?.onNextMobile();
}
}
}
+30 -25
View File
@@ -214,7 +214,7 @@ class EmailView extends GetWidget<SingleEmailController> {
);
}
if (controller.currentEmail == null || !isInsideThreadDetailView) {
if (controller.currentEmail == null || isFirstEmailInThreadDetail) {
return const BoxDecoration(color: Colors.white);
}
@@ -278,35 +278,38 @@ class EmailView extends GetWidget<SingleEmailController> {
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
isInsideThreadDetailView: isInsideThreadDetailView,
emailLoaded: controller.currentEmailLoaded.value,
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
mailboxContain: controller.getMailboxContain(presentationEmail),
presentationEmail: presentationEmail,
position: position,
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
username: controller.session?.username,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
additionalActions: [
EmailActionType.forward,
EmailActionType.replyAll,
EmailActionType.replyToList,
EmailActionType.printAll,
if (controller.responsiveUtils.isMobile(context))
EmailActionType.moveToMailbox,
if (!controller.responsiveUtils.isDesktop(context)) ...[
EmailActionType.markAsStarred,
EmailActionType.unMarkAsStarred,
EmailActionType.moveToTrash,
EmailActionType.deletePermanently,
],
],
emailIsRead: presentationEmail.hasRead,
),
onMoreActionClick: controller.isOnlyEmailInThread
? null
: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
mailboxContain: controller.getMailboxContain(presentationEmail),
presentationEmail: presentationEmail,
position: position,
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
username: controller.session?.username,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
additionalActions: [
EmailActionType.forward,
EmailActionType.replyAll,
EmailActionType.replyToList,
EmailActionType.printAll,
if (controller.responsiveUtils.isMobile(context))
EmailActionType.moveToMailbox,
if (!controller.responsiveUtils.isDesktop(context)) ...[
EmailActionType.markAsStarred,
EmailActionType.unMarkAsStarred,
EmailActionType.moveToTrash,
EmailActionType.deletePermanently,
],
],
emailIsRead: presentationEmail.hasRead,
),
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
mailboxContain: presentationEmail.findMailboxContain(
controller.mailboxDashBoardController.mapMailboxById,
),
isOnlyEmailInThread: controller.isOnlyEmailInThread,
)),
Obx(() => MailUnsubscribedBanner(
presentationEmail: controller.currentEmail,
@@ -449,6 +452,7 @@ class EmailView extends GetWidget<SingleEmailController> {
maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight,
onMailtoDelegateAction: controller.openMailToLink,
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
onScrollHorizontalEnd: controller.onScrollHorizontalEnd,
keepAlive: isInsideThreadDetailView,
enableQuoteToggle: isInsideThreadDetailView,
);
@@ -489,6 +493,7 @@ class EmailView extends GetWidget<SingleEmailController> {
onMailtoDelegateAction: controller.openMailToLink,
keepAlive: isInsideThreadDetailView,
enableQuoteToggle: isInsideThreadDetailView,
onScrollHorizontalEnd: controller.onScrollHorizontalEnd,
);
})
);
@@ -548,7 +548,7 @@ class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMi
: EmailActionType.markAsStarred,
if (additionalActions.contains(EmailActionType.moveToTrash) &&
additionalActions.contains(EmailActionType.deletePermanently))
_canDeletePermanently(presentationEmail)
_canDeletePermanently(presentationEmail, mailboxContain)
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash,
if (emailIsRead)
@@ -598,8 +598,13 @@ class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMi
}
}
bool _canDeletePermanently(PresentationEmail email) {
return email.mailboxContain?.isTrash
bool _canDeletePermanently(
PresentationEmail email,
PresentationMailbox? mailboxContain,
) {
return mailboxContain?.isTrash
?? mailboxContain?.isSpam
?? email.mailboxContain?.isTrash
?? email.mailboxContain?.isSpam
?? false;
}
@@ -34,6 +34,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
final EdgeInsetsGeometry? iconPadding;
final EdgeInsetsGeometry? iconMargin;
final EdgeInsetsGeometry? padding;
final bool isOnlyEmailInThread;
EmailViewAppBarWidget({
Key? key,
@@ -51,6 +52,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
this.iconPadding,
this.iconMargin,
this.padding,
this.isOnlyEmailInThread = false,
}) : super(key: key);
@override
@@ -251,19 +253,22 @@ class EmailViewAppBarWidget extends StatelessWidget {
_getMoveEmailButton(appLocalizations),
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
_getMoreButton(appLocalizations, isScreenWithShortestSide),
if (onMoreActionClick != null)
_getMoreButton(appLocalizations, isScreenWithShortestSide),
];
}
return [
_getReplyButton(appLocalizations),
if (!isOnlyEmailInThread)
_getReplyButton(appLocalizations),
if (!isResponsiveMobile)
_getMoveEmailButton(appLocalizations),
if (isResponsiveDesktop) ...[
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
],
_getMoreButton(appLocalizations, isScreenWithShortestSide),
if (onMoreActionClick != null)
_getMoreButton(appLocalizations, isScreenWithShortestSide),
];
}
}
@@ -5,7 +5,6 @@ import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/image/avatar_builder.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -42,6 +41,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
final OnTapAvatarActionClick? onTapAvatarActionClick;
final PresentationMailbox? mailboxContain;
final bool showUnreadVisualization;
final bool isOnlyEmailInThread;
const InformationSenderAndReceiverBuilder({
Key? key,
@@ -61,6 +61,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
this.onTapAvatarActionClick,
this.mailboxContain,
this.showUnreadVisualization = false,
this.isOnlyEmailInThread = false,
}) : super(key: key);
@override
@@ -164,9 +165,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
SizedBox(
height: IconUtils.defaultIconSize,
child: OverflowBox(
maxHeight: PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, responsiveUtils)
: EmailViewAppBarWidgetStyles.height,
maxHeight: EmailViewAppBarWidgetStyles.height,
fit: OverflowBoxFit.deferToChild,
child: EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
@@ -183,6 +182,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
iconPadding: const EdgeInsets.all(8),
iconMargin: EdgeInsets.zero,
padding: EdgeInsets.zero,
isOnlyEmailInThread: isOnlyEmailInThread,
),
),
),