Fix avoid creating a white overlay on email content when scrolling the thread

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-24 11:20:09 +07:00
committed by Dat H. Pham
parent f4b353a38d
commit 9b5fa6ce3f
2 changed files with 306 additions and 273 deletions
+151 -124
View File
@@ -63,148 +63,175 @@ class EmailView extends GetWidget<SingleEmailController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ColoredBox( final bodyWidget = Container(
color: controller.responsiveUtils.isWebDesktop(context) decoration: _getDecorationEmailView(context),
? AppColor.colorBgDesktop margin: _getMarginEmailView(context),
: Colors.white, child: Obx(() {
child: SafeArea( final currentEmailListener = Rxn(controller.currentEmail);
right: controller.responsiveUtils.isLandscapeMobile(context), final currentEmail = currentEmailListener.value;
left: controller.responsiveUtils.isLandscapeMobile(context), if (currentEmail != null) {
bottom: !PlatformInfo.isIOS, return Column(
child: Container( children: [
clipBehavior: Clip.antiAlias, if (!isInsideThreadDetailView)
decoration: _getDecorationEmailView(context), Obx(
margin: _getMarginEmailView(context), () => EmailViewAppBarWidget(
child: Obx(() {
final currentEmailListener = Rxn(controller.currentEmail);
final currentEmail = currentEmailListener.value;
if (currentEmail != null) {
return Column(children: [
if (!isInsideThreadDetailView)
Obx(() => EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'), key: const Key('email_view_app_bar_widget'),
presentationEmail: currentEmail, presentationEmail: currentEmail,
mailboxContain: _getMailboxContain(currentEmail), mailboxContain: _getMailboxContain(currentEmail),
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning, isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
onBackAction: () => controller.closeEmailView(context: context), onBackAction: () => controller.closeEmailView(context: context),
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action), onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction( onMoreActionClick: (presentationEmail, position) {
mailboxContain: controller.getMailboxContain(presentationEmail), return controller.emailActionReactor.handleMoreEmailAction(
presentationEmail: presentationEmail, mailboxContain: controller.getMailboxContain(presentationEmail),
position: position, presentationEmail: presentationEmail,
responsiveUtils: controller.responsiveUtils, position: position,
imagePaths: controller.imagePaths, responsiveUtils: controller.responsiveUtils,
username: controller.session?.username, imagePaths: controller.imagePaths,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action), username: controller.session?.username,
additionalActions: [], handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
emailIsRead: presentationEmail.hasRead, additionalActions: [],
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu, emailIsRead: presentationEmail.hasRead,
openPopupMenu: controller.mailboxDashBoardController.openPopupMenu, openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
), openPopupMenu: controller.mailboxDashBoardController.openPopupMenu,
);
},
supportBackAction: !isInsideThreadDetailView, supportBackAction: !isInsideThreadDetailView,
appBarDecoration: isInsideThreadDetailView appBarDecoration: isInsideThreadDetailView
? const BoxDecoration(border: Border(bottom: BorderSide( ? const BoxDecoration(
color: AppColor.colorDividerEmailView, border: Border(
))) bottom: BorderSide(
: null, color: AppColor.colorDividerEmailView,
),
),
)
: null,
emailLoaded: controller.currentEmailLoaded.value, emailLoaded: controller.currentEmailLoaded.value,
isInsideThreadDetailView: isInsideThreadDetailView, isInsideThreadDetailView: isInsideThreadDetailView,
)), ),
Obx(() { ),
final vacation = controller.mailboxDashBoardController.vacationResponse.value; Obx(() {
if (vacation?.vacationResponderIsValid == true && final vacation = controller.mailboxDashBoardController.vacationResponse.value;
(
controller.responsiveUtils.isMobile(context) || bool isPlatformSupportVacationVisible =
controller.responsiveUtils.isMobile(context) ||
controller.responsiveUtils.isTablet(context) || controller.responsiveUtils.isTablet(context) ||
controller.responsiveUtils.isLandscapeMobile(context) controller.responsiveUtils.isLandscapeMobile(context);
)
) { bool isVacationBannerVisible =
return VacationNotificationMessageWidget( vacation?.vacationResponderIsValid == true &&
margin: const EdgeInsetsDirectional.only(start: 12, end: 12, top: 8), isPlatformSupportVacationVisible;
vacationResponse: vacation!,
actionGotoVacationSetting: controller.mailboxDashBoardController.goToVacationSetting, if (isVacationBannerVisible) {
actionEndNow: controller.mailboxDashBoardController.disableVacationResponder return VacationNotificationMessageWidget(
); margin: const EdgeInsetsDirectional.only(
} else { start: 12,
return const SizedBox.shrink(); end: 12,
} top: 8,
}), ),
OptionalExpanded( vacationResponse: vacation!,
expandedEnabled: !isInsideThreadDetailView, actionGotoVacationSetting: controller.mailboxDashBoardController.goToVacationSetting,
child: LayoutBuilder(builder: (context, constraints) { actionEndNow: controller.mailboxDashBoardController.disableVacationResponder,
if (PlatformInfo.isMobile) { );
return OptionalScroll( } else {
scrollEnabled: !isInsideThreadDetailView, return const SizedBox.shrink();
child: Container( }
width: double.infinity, }),
alignment: Alignment.center, OptionalExpanded(
color: Colors.white, expandedEnabled: !isInsideThreadDetailView,
child: Obx(() => _buildEmailMessage( child: LayoutBuilder(builder: (context, constraints) {
if (PlatformInfo.isMobile) {
return OptionalScroll(
scrollEnabled: !isInsideThreadDetailView,
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: Obx(
() => _buildEmailMessage(
context: context, context: context,
presentationEmail: currentEmail, presentationEmail: currentEmail,
calendarEvent: controller.calendarEvent, calendarEvent: controller.calendarEvent,
maxBodyHeight: constraints.maxHeight maxBodyHeight: constraints.maxHeight,
)) ),
) ),
); ),
} else { );
return Obx(() { } else {
final calendarEvent = controller.calendarEvent; return Obx(() {
if (currentEmail.hasCalendarEvent && calendarEvent != null) { final calendarEvent = controller.calendarEvent;
return Padding( if (currentEmail.hasCalendarEvent && calendarEvent != null) {
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4), return Padding(
child: OptionalScroll( padding: const EdgeInsetsDirectional.symmetric(
scrollEnabled: !isInsideThreadDetailView, horizontal: 4,
child: Container( ),
width: double.infinity, child: OptionalScroll(
alignment: Alignment.center, scrollEnabled: !isInsideThreadDetailView,
color: Colors.white, child: Container(
child: _buildEmailMessage( width: double.infinity,
context: context, alignment: Alignment.center,
presentationEmail: currentEmail, color: Colors.white,
calendarEvent: calendarEvent, child: _buildEmailMessage(
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), context: context,
maxBodyHeight: constraints.maxHeight presentationEmail: currentEmail,
) calendarEvent: calendarEvent,
) emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
maxBodyHeight: constraints.maxHeight,
),
), ),
); ),
} else { );
return _buildEmailMessage( } else {
context: context, return _buildEmailMessage(
presentationEmail: currentEmail, context: context,
maxBodyHeight: constraints.maxHeight presentationEmail: currentEmail,
); maxBodyHeight: constraints.maxHeight,
} );
}); }
} });
}),
),
Obx(() {
final emailLoaded = controller.currentEmailLoaded.value;
if (emailLoaded == null || isInsideThreadDetailView) {
return const SizedBox.shrink();
} }
return EmailViewBottomBarWidget(
key: const Key('email_view_button_bar'),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
emailLoaded: emailLoaded,
presentationEmail: currentEmail,
userName: controller.getOwnEmailAddress(),
emailActionCallback: controller.pressEmailAction,
);
}), }),
]); ),
} else { Obx(() {
return const EmailViewEmptyWidget(); final emailLoaded = controller.currentEmailLoaded.value;
}
}) if (emailLoaded == null || isInsideThreadDetailView) {
) return const SizedBox.shrink();
) }
return EmailViewBottomBarWidget(
key: const Key('email_view_button_bar'),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
emailLoaded: emailLoaded,
presentationEmail: currentEmail,
userName: controller.getOwnEmailAddress(),
emailActionCallback: controller.pressEmailAction,
);
}),
],
);
} else {
return const EmailViewEmptyWidget();
}
}),
); );
if (PlatformInfo.isMobile) {
return ColoredBox(
color: controller.responsiveUtils.isWebDesktop(context)
? AppColor.colorBgDesktop
: Colors.white,
child: SafeArea(
right: controller.responsiveUtils.isLandscapeMobile(context),
left: controller.responsiveUtils.isLandscapeMobile(context),
bottom: !PlatformInfo.isIOS,
child: bodyWidget,
),
);
} else {
return bodyWidget;
}
} }
BoxDecoration _getDecorationEmailView(BuildContext context) { BoxDecoration _getDecorationEmailView(BuildContext context) {
@@ -29,178 +29,184 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final child = SelectionArea(child: SafeArea( Widget bodyWidget = Column(
child: Column( children: [
children: [ Obx(() {
Obx(() { final isLoading = showLoadingView(controller.viewState.value);
final isLoading = showLoadingView(controller.viewState.value);
return ThreadDetailAppBar( return ThreadDetailAppBar(
responsiveUtils: controller.responsiveUtils, responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths, imagePaths: controller.imagePaths,
isSearchRunning: controller.isSearchRunning, isSearchRunning: controller.isSearchRunning,
closeThreadDetailAction: controller.closeThreadDetailAction, closeThreadDetailAction: controller.closeThreadDetailAction,
lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull, lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull,
ownUserName: controller.session?.getOwnEmailAddress() ?? '', ownUserName: controller.session?.getOwnEmailAddress() ?? '',
mailboxContain: _getMailboxContain(), mailboxContain: _getMailboxContain(),
onEmailActionClick: isLoading onEmailActionClick: isLoading
? null ? null
: controller.threadDetailOnEmailActionClick, : controller.threadDetailOnEmailActionClick,
onMoreActionClick: (presentationEmail, position) => isLoading onMoreActionClick: (presentationEmail, position) => isLoading
? null ? null
: controller.emailActionReactor.handleMoreEmailAction( : controller.emailActionReactor.handleMoreEmailAction(
mailboxContain: controller.getThreadDetailEmailMailboxContains( mailboxContain: controller.getThreadDetailEmailMailboxContains(
presentationEmail, presentationEmail,
), ),
presentationEmail: presentationEmail, presentationEmail: presentationEmail,
position: position, position: position,
responsiveUtils: controller.responsiveUtils, responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths, imagePaths: controller.imagePaths,
username: controller.session?.username, username: controller.session?.username,
handleEmailAction: controller.threadDetailOnEmailActionClick, handleEmailAction: controller.threadDetailOnEmailActionClick,
additionalActions: [ additionalActions: [
if (controller.responsiveUtils.isMobile(context)) ...[ if (controller.responsiveUtils.isMobile(context)) ...[
EmailActionType.forward, EmailActionType.forward,
EmailActionType.replyAll, EmailActionType.replyAll,
EmailActionType.replyToList, EmailActionType.replyToList,
], ],
EmailActionType.markAsStarred, EmailActionType.markAsStarred,
EmailActionType.unMarkAsStarred, EmailActionType.unMarkAsStarred,
EmailActionType.moveToTrash, EmailActionType.moveToTrash,
EmailActionType.deletePermanently, EmailActionType.deletePermanently,
EmailActionType.printAll, EmailActionType.printAll,
EmailActionType.moveToMailbox, EmailActionType.moveToMailbox,
],
emailIsRead: presentationEmail.hasRead,
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
openPopupMenu: controller.mailboxDashBoardController.openPopupMenu,
),
optionWidgets: [
if (controller.previousAvailable)
TMailButtonWidget.fromIcon(
icon: controller.imagePaths.icNewer,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).newer,
backgroundColor: Colors.transparent,
onTapActionCallback: controller.onPrevious,
),
if (controller.nextAvailable)
TMailButtonWidget.fromIcon(
icon: controller.imagePaths.icOlder,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).older,
backgroundColor: Colors.transparent,
onTapActionCallback: controller.onNext,
),
], ],
); emailIsRead: presentationEmail.hasRead,
}), openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
Obx(() { openPopupMenu: controller.mailboxDashBoardController.openPopupMenu,
final nonPageViewThread = Expanded( ),
child: Container( optionWidgets: [
color: Colors.white, if (controller.previousAvailable)
padding: _padding(context), TMailButtonWidget.fromIcon(
child: SingleChildScrollView( icon: controller.imagePaths.icNewer,
controller: controller.scrollController, iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
child: Column( iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).newer,
backgroundColor: Colors.transparent,
onTapActionCallback: controller.onPrevious,
),
if (controller.nextAvailable)
TMailButtonWidget.fromIcon(
icon: controller.imagePaths.icOlder,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).older,
backgroundColor: Colors.transparent,
onTapActionCallback: controller.onNext,
),
],
);
}),
Obx(() {
final nonPageViewThread = Expanded(
child: Container(
color: Colors.white,
padding: _padding(context),
child: SingleChildScrollView(
controller: controller.scrollController,
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: controller.getThreadDetailEmailViews() children: controller.getThreadDetailEmailViews()
),
), ),
), ),
); ),
);
if (PlatformInfo.isMobile) { if (PlatformInfo.isMobile) {
final manager = controller.threadDetailManager; final manager = controller.threadDetailManager;
final currentIndex = manager.currentMobilePageViewIndex.value; final currentIndex = manager.currentMobilePageViewIndex.value;
if (currentIndex == -1) return nonPageViewThread; if (currentIndex == -1) return nonPageViewThread;
return Expanded( return Expanded(
child: PageView.builder( child: PageView.builder(
controller: manager.pageController, controller: manager.pageController,
itemCount: manager.isThreadDetailEnabled itemCount: manager.isThreadDetailEnabled
? manager.availableThreadIds.length ? manager.availableThreadIds.length
: manager.currentDisplayedEmails.length, : manager.currentDisplayedEmails.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index != currentIndex) { if (index != currentIndex) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: controller.getThreadDetailEmailViews() children: controller.getThreadDetailEmailViews()
),
);
},
onPageChanged: controller.onThreadPageChanged,
),
);
}
return nonPageViewThread;
}),
Obx(() {
final expandedEmailId = controller.currentExpandedEmailId.value;
if (expandedEmailId == null) {
return const SizedBox.shrink();
}
final expandedPresentationEmail = controller.emailIdsPresentation[expandedEmailId];
if (expandedPresentationEmail == null) {
return const SizedBox.shrink();
}
final currentEmailLoaded = controller.currentEmailLoaded.value;
if (currentEmailLoaded == null) {
return const SizedBox.shrink();
}
return Padding(
padding: controller.responsiveUtils.isDesktop(context)
? const EdgeInsetsDirectional.only(end: 16)
: EdgeInsets.zero,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(20),
),
child: EmailViewBottomBarWidget(
key: const Key('email_view_button_bar'),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
emailLoaded: currentEmailLoaded,
presentationEmail: expandedPresentationEmail,
userName: controller.session?.getOwnEmailAddress() ?? '',
emailActionCallback: (action, email) {
controller.mailboxDashBoardController
..dispatchEmailUIAction(PerformEmailActionInThreadDetailAction(
emailActionType: action,
presentationEmail: email,
))
..dispatchEmailUIAction(EmailUIAction());
},
bottomBarDecoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: AppColor.colorDividerEmailView),
), ),
), );
padding: EdgeInsets.zero, },
), onPageChanged: controller.onThreadPageChanged,
), ),
); );
}), }
if (controller.responsiveUtils.isDesktop(context))
const SizedBox(height: 16), return nonPageViewThread;
], }),
), Obx(() {
)); final expandedEmailId = controller.currentExpandedEmailId.value;
if (expandedEmailId == null) {
return const SizedBox.shrink();
}
final expandedPresentationEmail = controller.emailIdsPresentation[expandedEmailId];
if (expandedPresentationEmail == null) {
return const SizedBox.shrink();
}
final currentEmailLoaded = controller.currentEmailLoaded.value;
if (currentEmailLoaded == null) {
return const SizedBox.shrink();
}
return Padding(
padding: controller.responsiveUtils.isDesktop(context)
? const EdgeInsetsDirectional.only(end: 16)
: EdgeInsets.zero,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(20),
),
child: EmailViewBottomBarWidget(
key: const Key('email_view_button_bar'),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
emailLoaded: currentEmailLoaded,
presentationEmail: expandedPresentationEmail,
userName: controller.session?.getOwnEmailAddress() ?? '',
emailActionCallback: (action, email) {
controller.mailboxDashBoardController
..dispatchEmailUIAction(PerformEmailActionInThreadDetailAction(
emailActionType: action,
presentationEmail: email,
))
..dispatchEmailUIAction(EmailUIAction());
},
bottomBarDecoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: AppColor.colorDividerEmailView),
),
),
padding: EdgeInsets.zero,
),
),
);
}),
if (controller.responsiveUtils.isDesktop(context))
const SizedBox(height: 16),
],
);
if (PlatformInfo.isMobile) {
bodyWidget = SafeArea(child: bodyWidget);
}
if (PlatformInfo.isWeb) {
bodyWidget = SelectionArea(child: bodyWidget);
}
return ClipRRect( return ClipRRect(
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(20)), borderRadius: const BorderRadius.vertical(bottom: Radius.circular(20)),
child: child, child: bodyWidget,
); );
} }