TF-3643 Thread Detail get emails meta data method

TF-3643 Thread Detail cache email with thread id

TF-3643 Thread Detail view thread detail

TF-3643 Thread Detail [Part 2 fixup] remove ThreadDetailController dependency from MailboxDashboardController

TF-3643 Thread Detail [Part 2 fixup] remove EmailSupervisorController dependency from ThreadDetailController

TF-3643 Thread Detail [Part 2 fixup] move isSearchRunning check into ThreadDetailController

TF-3643 Thread Detail [Part 2 fixup] ensure GetEmailsByIdsInteractor performance

TF-3643 Thread Detail [Part 2 fixup] unit test loadMoreThreadDetailEmails

TF-3643 Thread Detail [Part 2 fixup] unit test getEmailIdsToLoad

TF-3643 Thread Detail [Part 2 fixup] optimize ThreadDetailBinding bindingsDataSource

TF-3643 Thread Detail [Part 2 fixup] optimize getThreadDetailLoadingView

TF-3643 Thread Detail [Part 2 fixup] readd unique ValueKey for HtmlContentViewerOnWebWidget

TF-3643 Thread Detail [Part 2 fixup] unit test initializeThreadDetailEmails

TF-3643 Thread Detail [Part 3 fixup] Update private action button methods

TF-3643 Thread Detail [Part 4 fixup] Collapsed email preview 65 characters max

TF-3643 Thread Detail [Part 4 fixup] Remove personal scripts

TF-3643 Thread Detail move EmailInThreadStatus into PresentationEmail

TF-3643 Thread Detail refactor toggle collapse expand

TF-3643 Thread Detail EmailViewAppBarWidget required parameters

TF-3643 Thread Detail tap avatar toggle expansion

TF-3643 Thread Detail fix cannot open single email

TF-3643 Thread Detail fix open thread first time blank view

TF-3643 Thread Detail fix thread detail UI

TF-3643 Thread Detail fix round bottom view

TF-3643 Thread Detail fix round bottom view

TF-3643 Thread Detail bottom actions depend on current expanded email

TF-3643 Thread Detail hide next previous actions

TF-3643 Thread Detail fix load more show initial loading view

TF-3643 Thread Detail fix cannot view single email

TF-3643 Thread Detail fix bottom place holder not aligned

TF-3643 Thread Detail fix back to search view

TF-3643 Thread Detail fix display 65 character preview collapsed email

TF-3643 Thread Detail fix search not work

TF-3643 Thread Detail fix bottom place holder UI

TF-3643 Thread Detail fix expand view

TF-3643 Thread Detail jump to top of last email view

TF-3643 Thread Detail remove toMe condition of filtering thread

TF-3643 Thread Detail fix sort thread detail

TF-3643 Thread Detail fix assert not work in release

TF-3643 Thread Detail add load more test case <20

TF-3643 Thread Detail remove the use of currentThreadId

TF-3643 Thread Detail remove the use of selectedEmail in SingleEmailController

TF-3643 Thread Detail fix unit test SingleEmailController calendar

TF-3643 Thread Detail disable 2 way scroll control

TF-3643 Thread Detail allow thread to load sent email incase thread only contains 1 email

TF-3643 Thread Detail disable single email page view

TF-3643 Thread Detail update emailsNotLoadedCount name

TF-3643 Thread Detail remove collapsed email char limit

TF-3643 Thread Detail fix CI

TF-3643 Thread Detail unit test handleGetEmailIdsByThreadIdSuccess

TF-3643 Thread Detail make _currentEmailId private

TF-3643 Thread Detail fix search click users

TF-3643 Thread Detail fix scroll jump

TF-3643 Thread Detail fix scroll jump

TF-3643 Thread Detail fix web back action

TF-3643 Thread Detail round bottom actions

TF-3643 Thread Detail fix tablet large empty email

TF-3643 Thread Detail fix email subject selectable

TF-3643 Thread Detail fix email single view with top divider

TF-3643 Thread Detail fix email cache threadId missing

TF-3643 Thread Detail fix filter sent email in thread details

TF-3643 Thread Detail fix select text

TF-3643 Thread Detail fix subject alignment

TF-3643 Thread Detail fix subject alignment

TF-3643 Thread Detail fix tablet large empty email

TF-3643 Thread Detail add nullcheck for _jumpScrollViewToTopOfEmail

TF-3643 Thread Detail decouple ThreadDetailController from SingleEmailController

TF-3643 Thread Detail combine emailIds to emailIdsPresentation

TF-3643 Thread Detail remove currentEmailLoaded obx listener of SingleEmailController

TF-3643 Thread Detail replace threadAction with an email ui action

TF-3643 Thread Detail fix top and bottom bar delay displaying

TF-3643 Thread Detail update ReplyForwardBottomBarAction to PerformEmailActionInThreadDetailAction

TF-3643 Thread Detail use getOwnEmailAddress in ThreadDetailAppBar

TF-3643 Thread Detail add TextOverflow.ellipsis to EmailSubjectWidget

TF-3643 Thread Detail fix round border threadDetailLoadingView
This commit is contained in:
DatDang
2025-05-12 15:22:29 +07:00
committed by Dat H. Pham
parent 7c3af832e4
commit 58a576adeb
84 changed files with 2845 additions and 558 deletions
@@ -21,6 +21,7 @@ class AttachmentItemWidget extends StatelessWidget {
final Attachment attachment;
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
final OnViewAttachmentFileAction? viewAttachmentAction;
final String? singleEmailControllerTag;
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
@@ -30,13 +31,14 @@ class AttachmentItemWidget extends StatelessWidget {
required this.attachment,
this.downloadAttachmentAction,
this.viewAttachmentAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(
() {
final controller = Get.find<SingleEmailController>();
final controller = Get.find<SingleEmailController>(tag: singleEmailControllerTag);
final attachmentsViewState = controller.attachmentsViewState;
bool isLoading = false;
if (attachment.blobId != null) {
@@ -21,6 +21,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
final OnCancelButtonAction? onCancelButtonAction;
final OnCloseButtonAction? onCloseButtonAction;
final String? singleEmailControllerTag;
const AttachmentListBottomSheetBodyBuilder({
super.key,
@@ -33,6 +34,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
this.onViewAttachmentFileAction,
this.onCancelButtonAction,
this.onCloseButtonAction,
this.singleEmailControllerTag,
});
@override
@@ -105,6 +107,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
attachment: attachments[index],
downloadAttachmentAction: onDownloadAttachmentFileAction,
viewAttachmentAction: onViewAttachmentFileAction,
singleEmailControllerTag: singleEmailControllerTag,
);
},
separatorBuilder: (context, index) {
@@ -15,6 +15,7 @@ class AttachmentListBottomSheetBuilder {
final List<Attachment> _attachments;
final ImagePaths _imagePaths;
final ScrollController _scrollController;
final String? _singleEmailControllerTag;
late double _statusBarHeight;
@@ -29,6 +30,7 @@ class AttachmentListBottomSheetBuilder {
this._attachments,
this._imagePaths,
this._scrollController,
this._singleEmailControllerTag,
) {
_statusBarHeight = Get.statusBarHeight / MediaQuery.of(_context).devicePixelRatio;
}
@@ -70,6 +72,7 @@ class AttachmentListBottomSheetBuilder {
onViewAttachmentFileAction: _onViewAttachmentFileAction,
onCancelButtonAction: _onCancelButtonAction,
onCloseButtonAction: _onCloseButtonAction,
singleEmailControllerTag: _singleEmailControllerTag,
),
);
}
@@ -22,6 +22,7 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
final OnCancelButtonAction? onCancelButtonAction;
final OnCloseButtonAction? onCloseButtonAction;
final String? singleEmailControllerTag;
const AttachmentListDialogBodyBuilder({
super.key,
@@ -35,7 +36,8 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
this.onDownloadAttachmentFileAction,
this.onViewAttachmentFileAction,
this.onCancelButtonAction,
this.onCloseButtonAction
this.onCloseButtonAction,
this.singleEmailControllerTag,
});
@override
@@ -113,6 +115,7 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
attachment: attachments[index],
downloadAttachmentAction: onDownloadAttachmentFileAction,
viewAttachmentAction: onViewAttachmentFileAction,
singleEmailControllerTag: singleEmailControllerTag,
);
},
separatorBuilder: (context, index) {
@@ -22,6 +22,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
final OnCancelButtonAction? onCancelButtonAction;
final OnCloseButtonAction? onCloseButtonAction;
final String? singleEmailControllerTag;
const AttachmentListDialogBuilder({
Key? key,
@@ -37,6 +38,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
this.onViewAttachmentFileAction,
this.onCancelButtonAction,
this.onCloseButtonAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
@@ -61,6 +63,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
onViewAttachmentFileAction: onViewAttachmentFileAction,
onCancelButtonAction: onCancelButtonAction,
onCloseButtonAction: onCloseButtonAction,
singleEmailControllerTag: singleEmailControllerTag,
),
);
}
@@ -19,6 +19,7 @@ class AttachmentListItemWidget extends StatelessWidget {
final Attachment attachment;
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
final OnViewAttachmentFileAction? viewAttachmentAction;
final String? singleEmailControllerTag;
final _imagePaths = Get.find<ImagePaths>();
@@ -27,13 +28,14 @@ class AttachmentListItemWidget extends StatelessWidget {
required this.attachment,
this.downloadAttachmentAction,
this.viewAttachmentAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(
() {
final controller = Get.find<SingleEmailController>();
final controller = Get.find<SingleEmailController>(tag: singleEmailControllerTag);
final attachmentsViewState = controller.attachmentsViewState;
bool isLoading = false;
if (attachment.blobId != null) {
@@ -1,3 +1,4 @@
import 'dart:math';
import 'package:core/presentation/constants/constants_ui.dart';
import 'package:core/presentation/extensions/color_extension.dart';
@@ -9,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/event_description_detail_widget_styles.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
@@ -44,15 +46,17 @@ class EventBodyContentWidget extends StatelessWidget {
child: Stack(
children: [
if (PlatformInfo.isWeb)
Container(
constraints: const BoxConstraints(maxHeight: EventDescriptionDetailWidgetStyles.maxHeight),
Padding(
padding: const EdgeInsetsDirectional.only(end: EventDescriptionDetailWidgetStyles.webContentPadding),
child: LayoutBuilder(builder: (context, constraints) {
return Stack(
children: [
HtmlContentViewerOnWeb(
widthContent: constraints.maxWidth,
heightContent: constraints.maxHeight,
heightContent: min(
constraints.maxHeight,
EmailViewStyles.initialHtmlViewHeight,
),
contentHtml: content,
mailtoDelegate: onMailtoDelegateAction,
direction: AppUtils.getCurrentDirection(context),
@@ -14,6 +14,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
final OnDragAttachmentEnd? onDragEnd;
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
final OnViewAttachmentFileAction? viewAttachmentAction;
final String? singleEmailControllerTag;
const DraggableAttachmentItemWidget({
Key? key,
@@ -22,6 +23,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
this.onDragEnd,
this.downloadAttachmentAction,
this.viewAttachmentAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
@@ -35,6 +37,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
attachment: attachment,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
),
);
}
@@ -27,6 +27,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
final OnTapActionCallback? onTapShowAllAttachmentFile;
final bool showDownloadAllAttachmentsButton;
final OnTapActionCallback? onTapDownloadAllButton;
final String? singleEmailControllerTag;
const EmailAttachmentsWidget({
super.key,
@@ -40,6 +41,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
this.onTapShowAllAttachmentFile,
this.showDownloadAllAttachmentsButton = false,
this.onTapDownloadAllButton,
this.singleEmailControllerTag,
});
Widget _buildMoreAttachmentButton(
@@ -108,17 +110,19 @@ class EmailAttachmentsWidget extends StatelessWidget {
children: attachmentDisplayed.map((attachment) {
if (PlatformInfo.isWeb) {
return DraggableAttachmentItemWidget(
attachment: attachment,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
attachment: attachment,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
} else {
return AttachmentItemWidget(
attachment: attachment,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
attachment: attachment,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
}
}).toList(),
@@ -56,7 +56,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
Flexible(
child: Container(
constraints: BoxConstraints(maxHeight: _maxHeight),
child: ListView(
@@ -149,7 +149,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -14,12 +14,14 @@ class EmailSenderBuilder extends StatelessWidget {
final EmailAddress emailAddress;
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
final bool showSenderEmail;
const EmailSenderBuilder({
Key? key,
super.key,
required this.emailAddress,
this.openEmailAddressDetailAction,
}) : super(key: key);
this.showSenderEmail = true,
});
@override
Widget build(BuildContext context) {
@@ -27,7 +29,7 @@ class EmailSenderBuilder extends StatelessWidget {
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (emailAddress.displayName.isNotEmpty)
@@ -38,32 +40,30 @@ class EmailSenderBuilder extends StatelessWidget {
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
},
borderRadius: 8,
padding: const EdgeInsets.all(3),
padding: EdgeInsets.zero,
customStyle: ThemeUtils.textStyleHeadingHeadingSmall(
color: Colors.black,
),
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap
),
Padding(
padding: const EdgeInsets.only(top: 2),
child: MaterialTextButton(
if (showSenderEmail)
MaterialTextButton(
label: '<${emailAddress.emailAddress}>',
onTap: () => openEmailAddressDetailAction?.call(context, emailAddress),
onLongPress: () {
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
},
borderRadius: 8,
padding: const EdgeInsets.all(3),
padding: EdgeInsets.zero,
customStyle: ThemeUtils.textStyleBodyBody1(
color: AppColor.steelGray400,
),
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap
),
)
)
]
)
),
);
}
}
@@ -20,7 +20,9 @@ class EmailSubjectWidget extends StatelessWidget {
child: Text(
presentationEmail.getEmailTitle(),
maxLines: EmailSubjectStyles.maxLines,
style: ThemeUtils.textStyleHeadingH5(color: Colors.black)
style: ThemeUtils.textStyleHeadingH5(color: Colors.black).copyWith(
overflow: TextOverflow.ellipsis,
),
)
);
}
@@ -8,7 +8,7 @@ import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_back_button.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -19,7 +19,6 @@ typedef OnMoreActionClick = void Function(PresentationEmail, RelativeRect?);
class EmailViewAppBarWidget extends StatelessWidget {
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _singleEmailController = Get.find<SingleEmailController>();
final PresentationEmail presentationEmail;
final List<Widget>? optionsWidget;
@@ -28,29 +27,45 @@ class EmailViewAppBarWidget extends StatelessWidget {
final VoidCallback onBackAction;
final OnEmailActionClick? onEmailActionClick;
final OnMoreActionClick? onMoreActionClick;
final bool supportBackAction;
final BoxDecoration? appBarDecoration;
final EmailLoaded? emailLoaded;
final bool isInsideThreadDetailView;
final double? height;
final EdgeInsetsGeometry? iconPadding;
final EdgeInsetsGeometry? iconMargin;
final EdgeInsetsGeometry? padding;
EmailViewAppBarWidget({
Key? key,
required this.presentationEmail,
required this.onBackAction,
required this.isSearchActivated,
required this.emailLoaded,
this.mailboxContain,
this.onEmailActionClick,
this.onMoreActionClick,
this.optionsWidget,
this.supportBackAction = true,
this.appBarDecoration,
this.isInsideThreadDetailView = false,
this.height,
this.iconPadding,
this.iconMargin,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final child = LayoutBuilder(builder: (context, constraints) {
return Container(
height: PlatformInfo.isIOS
height: height ?? (PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.height,
padding: PlatformInfo.isIOS
: EmailViewAppBarWidgetStyles.height),
padding: padding ?? (PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.padding,
decoration: const BoxDecoration(
: EmailViewAppBarWidgetStyles.padding),
decoration: appBarDecoration ?? const BoxDecoration(
border: Border(
bottom: BorderSide(
color: EmailViewAppBarWidgetStyles.bottomBorderColor,
@@ -63,89 +78,30 @@ class EmailViewAppBarWidget extends StatelessWidget {
),
color: EmailViewAppBarWidgetStyles.backgroundColor,
),
child: Row(children: [
if (_supportDisplayMailboxNameTitle(context))
EmailViewBackButton(
imagePaths: _imagePaths,
onBackAction: onBackAction,
mailboxContain: mailboxContain,
isSearchActivated: isSearchActivated,
maxWidth: constraints.maxWidth,
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
if (_supportDisplayMailboxNameTitle(context) && supportBackAction)
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: EmailViewBackButton(
imagePaths: _imagePaths,
onBackAction: onBackAction,
mailboxContain: mailboxContain,
isSearchActivated: isSearchActivated,
maxWidth: constraints.maxWidth,
),
),
),
const Spacer(),
Row(
children: [
if (optionsWidget != null) ... optionsWidget!,
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoveEmail,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).move_message,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(presentationEmail, EmailActionType.moveToMailbox)
),
TMailButtonWidget.fromIcon(
icon: presentationEmail.hasStarred
? _imagePaths.icStar
: _imagePaths.icUnStar,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: presentationEmail.hasStarred
? null
: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: presentationEmail.hasStarred
? AppLocalizations.of(context).not_starred
: AppLocalizations.of(context).mark_as_starred,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
presentationEmail.hasStarred ? EmailActionType.unMarkAsStarred : EmailActionType.markAsStarred
)
),
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
Obx(() => AbsorbPointer(
absorbing: _singleEmailController.currentEmailLoaded.value == null,
child: TMailButtonWidget.fromIcon(
icon: _imagePaths.icPrinter,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).printAll,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.printAll,
),
),
)),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icDeleteComposer,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: canDeletePermanently
? AppLocalizations.of(context).delete_permanently
: AppLocalizations.of(context).move_to_trash,
onTapActionCallback: () {
if (canDeletePermanently) {
onEmailActionClick?.call(presentationEmail, EmailActionType.deletePermanently);
} else {
onEmailActionClick?.call(presentationEmail, EmailActionType.moveToTrash);
}
}
),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoreVertical,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).more,
onTapActionCallback: _responsiveUtils.isScreenWithShortestSide(context)
? () => onMoreActionClick?.call(presentationEmail, null)
: null,
onTapActionAtPositionCallback: !_responsiveUtils.isScreenWithShortestSide(context)
? (position) => onMoreActionClick?.call(presentationEmail, position)
: null
),
]
if (optionsWidget != null) ... optionsWidget!,
..._buildActionButtons(
appLocalizations: AppLocalizations.of(context),
isScreenWithShortestSide: _responsiveUtils.isScreenWithShortestSide(context),
isResponsiveMobile: _responsiveUtils.isMobile(context),
isResponsiveDesktop: _responsiveUtils.isDesktop(context),
),
]
),
])
);
@@ -154,7 +110,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
return PopScope(
canPop: false,
onPopInvokedWithResult: (_, __) {
if (!PlatformInfo.isAndroid) return;
if (PlatformInfo.isAndroid) return;
onBackAction();
},
child: child,
@@ -178,4 +134,137 @@ class EmailViewAppBarWidget extends StatelessWidget {
bool get canDeletePermanently {
return mailboxContain?.isTrash == true || mailboxContain?.isSpam == true;
}
Widget _getReplyButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icReply,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: appLocalizations.reply,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.reply,
),
padding: iconPadding,
margin: iconMargin,
);
Widget _getPrintButton(AppLocalizations appLocalizations) => AbsorbPointer(
absorbing: emailLoaded == null,
child: TMailButtonWidget.fromIcon(
icon: _imagePaths.icPrinter,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: appLocalizations.printAll,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.printAll,
),
padding: iconPadding,
margin: iconMargin,
),
);
Widget _getMoveEmailButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoveEmail,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: appLocalizations.move_message,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.moveToMailbox,
),
padding: iconPadding,
margin: iconMargin,
);
Widget _getMarkStarButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
icon: presentationEmail.hasStarred
? _imagePaths.icStar
: _imagePaths.icUnStar,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: presentationEmail.hasStarred
? null
: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: presentationEmail.hasStarred
? applocalizations.not_starred
: applocalizations.mark_as_starred,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
presentationEmail.hasStarred
? EmailActionType.unMarkAsStarred
: EmailActionType.markAsStarred
),
padding: iconPadding,
margin: iconMargin,
);
Widget _getDeleteButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icDeleteComposer,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: canDeletePermanently
? applocalizations.delete_permanently
: applocalizations.move_to_trash,
onTapActionCallback: () {
if (canDeletePermanently) {
onEmailActionClick?.call(presentationEmail, EmailActionType.deletePermanently);
} else {
onEmailActionClick?.call(presentationEmail, EmailActionType.moveToTrash);
}
},
padding: iconPadding,
margin: iconMargin,
);
Widget _getMoreButton(
AppLocalizations applocalizations,
bool isScreenWithShortestSide,
) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoreVertical,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: applocalizations.more,
onTapActionCallback: isScreenWithShortestSide
? () => onMoreActionClick?.call(presentationEmail, null)
: null,
onTapActionAtPositionCallback: !isScreenWithShortestSide
? (position) => onMoreActionClick?.call(presentationEmail, position)
: null,
padding: iconPadding,
margin: iconMargin,
);
List<Widget> _buildActionButtons({
required AppLocalizations appLocalizations,
required bool isScreenWithShortestSide,
required bool isResponsiveMobile,
required bool isResponsiveDesktop,
}) {
if (!isInsideThreadDetailView) {
return [
_getPrintButton(appLocalizations),
_getMoveEmailButton(appLocalizations),
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
_getMoreButton(appLocalizations, isScreenWithShortestSide),
];
}
return [
_getReplyButton(appLocalizations),
if (!isResponsiveMobile)
_getMoveEmailButton(appLocalizations),
if (isResponsiveDesktop) ...[
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
],
_getMoreButton(appLocalizations, isScreenWithShortestSide),
];
}
}
@@ -21,6 +21,8 @@ class EmailViewBottomBarWidget extends StatelessWidget {
final EmailLoaded emailLoaded;
final String userName;
final OnEmailActionCallback emailActionCallback;
final BoxDecoration? bottomBarDecoration;
final EdgeInsetsGeometry? padding;
const EmailViewBottomBarWidget({
Key? key,
@@ -30,6 +32,8 @@ class EmailViewBottomBarWidget extends StatelessWidget {
required this.emailLoaded,
required this.userName,
required this.emailActionCallback,
this.bottomBarDecoration,
this.padding,
}) : super(key: key);
@override
@@ -40,7 +44,7 @@ class EmailViewBottomBarWidget extends StatelessWidget {
);
return Container(
decoration: const BoxDecoration(
decoration: bottomBarDecoration ?? const BoxDecoration(
border: Border(
top: BorderSide(
color: EmailViewBottomBarWidgetStyles.topBorderColor,
@@ -53,7 +57,7 @@ class EmailViewBottomBarWidget extends StatelessWidget {
),
color: EmailViewBottomBarWidgetStyles.backgroundColor
),
padding: EmailViewBottomBarWidgetStyles.padding,
padding: padding ?? EmailViewBottomBarWidgetStyles.padding,
child: IntrinsicHeight(
child: Row(
children: [
@@ -1,14 +1,17 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/icon_utils.dart';
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:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:tmail_ui_user/features/base/widget/email_avatar_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/features/email/presentation/model/smime_signature_status.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_receiver_widget.dart';
@@ -27,6 +30,12 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
final OnEmailActionClick? onEmailActionClick;
final double? maxBodyHeight;
final SMimeSignatureStatus? sMimeStatus;
final bool isInsideThreadDetailView;
final EmailLoaded? emailLoaded;
final OnMoreActionClick? onMoreActionClick;
final bool showRecipients;
final VoidCallback? onToggleThreadDetailCollapseExpand;
final OnTapAvatarActionClick? onTapAvatarActionClick;
const InformationSenderAndReceiverBuilder({
Key? key,
@@ -38,66 +47,110 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
this.maxBodyHeight,
this.openEmailAddressDetailAction,
this.onEmailActionClick,
this.isInsideThreadDetailView = false,
this.emailLoaded,
this.onMoreActionClick,
this.showRecipients = true,
this.onToggleThreadDetailCollapseExpand,
this.onTapAvatarActionClick,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 16),
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: emailSelected.countRecipients > 0
crossAxisAlignment: emailSelected.countRecipients > 0 && (showRecipients || responsiveUtils.isMobile(context))
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
EmailAvatarBuilder(emailSelected: emailSelected),
EmailAvatarBuilder(
emailSelected: emailSelected,
onTapAvatarActionClick: onTapAvatarActionClick,
),
const SizedBox(width: 16),
Expanded(child: LayoutBuilder(builder: (context, constraints) {
return Transform(
transform: Matrix4.translationValues(0.0, -5.0, 0.0),
return InkWell(
onTap: onToggleThreadDetailCollapseExpand,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (emailSelected.from?.isNotEmpty == true)
Row(children: [
Expanded(child: Row(
children: [
Flexible(child: Transform(
transform: Matrix4.translationValues(-5.0, 0.0, 0.0),
child: EmailSenderBuilder(
emailAddress: emailSelected.from!.first,
openEmailAddressDetailAction: openEmailAddressDetailAction,
)
)),
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
Tooltip(
key: const Key('smime_signature_status_icon'),
message: sMimeStatus!.getTooltipMessage(context),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: SvgPicture.asset(
sMimeStatus!.getIcon(imagePaths),
fit: BoxFit.fill,
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Row(
children: [
if (emailSelected.from?.isNotEmpty == true)
Flexible(
child: EmailSenderBuilder(
emailAddress: emailSelected.from!.first,
openEmailAddressDetailAction: openEmailAddressDetailAction,
),
),
),
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).unsubscribe,
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.colorTextBody,
decoration: TextDecoration.underline,
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
Tooltip(
key: const Key('smime_signature_status_icon'),
message: sMimeStatus!.getTooltipMessage(context),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: SvgPicture.asset(
sMimeStatus!.getIcon(imagePaths),
fit: BoxFit.fill,
),
),
),
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
),
]
)),
ReceivedTimeBuilder(emailSelected: emailSelected),
]),
if (emailSelected.countRecipients > 0)
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).unsubscribe,
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.colorTextBody,
decoration: TextDecoration.underline,
),
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
),
if (isInsideThreadDetailView && !responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
),
],
),
),
if (!isInsideThreadDetailView && !responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
),
if (isInsideThreadDetailView)
EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
presentationEmail: emailSelected,
isSearchActivated: false,
onBackAction: () {},
onEmailActionClick: onEmailActionClick,
onMoreActionClick: onMoreActionClick,
supportBackAction: false,
appBarDecoration: const BoxDecoration(),
emailLoaded: emailLoaded,
isInsideThreadDetailView: isInsideThreadDetailView,
height: IconUtils.defaultIconSize,
iconPadding: EdgeInsets.zero,
iconMargin: const EdgeInsetsDirectional.only(start: 16),
padding: const EdgeInsets.symmetric(horizontal: 8),
),
],
),
if (responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(top: 5),
),
if (emailSelected.countRecipients > 0 && showRecipients)
EmailReceiverWidget(
emailSelected: emailSelected,
maxWidth: constraints.maxWidth,
@@ -10,16 +10,18 @@ import 'package:model/extensions/presentation_email_extension.dart';
class ReceivedTimeBuilder extends StatelessWidget {
final PresentationEmail emailSelected;
final EdgeInsetsGeometry? padding;
const ReceivedTimeBuilder({
Key? key,
required this.emailSelected,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16),
padding: padding ?? const EdgeInsetsDirectional.only(start: 16),
child: Text(
emailSelected.getReceivedAt(
Localizations.localeOf(context).toLanguageTag(),