TF-3881 Thread Detail Reply action only for collapsed email

TF-3881 Thread Detail Hide sender email on collapsed or mobile view

TF-3881 Thread Detail Show attachment icon on collapsed

TF-3881 Thread Detail Update date format of received email time

Thread Detail update context menu
This commit is contained in:
DatDang
2025-07-17 15:50:05 +07:00
committed by Dat H. Pham
parent 7332b731dc
commit 0c372d0465
9 changed files with 132 additions and 68 deletions
@@ -5,6 +5,7 @@ import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/email_in_thread_status.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
@@ -258,6 +259,10 @@ class EmailViewAppBarWidget extends StatelessWidget {
];
}
if (presentationEmail.emailInThreadStatus == EmailInThreadStatus.collapsed) {
return [_getReplyButton(appLocalizations)];
}
return [
if (!isOnlyEmailInThread)
_getReplyButton(appLocalizations),
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/email_in_thread_status.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
@@ -108,6 +109,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
child: EmailSenderBuilder(
emailAddress: emailSelected.from!.first,
openEmailAddressDetailAction: openEmailAddressDetailAction,
showSenderEmail: _showSenderEmail(responsiveUtils.isMobile(context)),
),
),
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
@@ -135,10 +137,21 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
),
if (_showAttachmentIcon() && !responsiveUtils.isMobile(context))
Padding(
padding: const EdgeInsetsDirectional.only(start: 16),
child: SvgPicture.asset(
imagePaths.icAttachment,
colorFilter: AppColor.colorAttachmentIcon.asFilter(),
width: 20,
height: 20,
),
),
if (isInsideThreadDetailView && !responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
padding: const EdgeInsetsDirectional.only(start: 8, top: 2),
showDaysAgo: _showDaysAgo(responsiveUtils.isMobile(context)),
),
if (showUnreadVisualization &&
!emailSelected.hasRead &&
@@ -160,6 +173,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
showDaysAgo: _showDaysAgo(responsiveUtils.isMobile(context)),
),
if (isInsideThreadDetailView)
SizedBox(
@@ -189,9 +203,24 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
],
),
if (responsiveUtils.isMobile(context) && isInsideThreadDetailView)
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.symmetric(vertical: 5),
Row(
children: [
if (_showAttachmentIcon())
Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: SvgPicture.asset(
imagePaths.icAttachment,
colorFilter: AppColor.colorAttachmentIcon.asFilter(),
width: 20,
height: 20,
),
),
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.symmetric(vertical: 5),
showDaysAgo: _showDaysAgo(responsiveUtils.isMobile(context)),
),
],
),
if (emailSelected.countRecipients > 0 && showRecipients)
EmailReceiverWidget(
@@ -208,4 +237,21 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
),
);
}
bool _showSenderEmail(bool isResponsiveMobile) {
return emailSelected.emailInThreadStatus == EmailInThreadStatus.expanded &&
!isResponsiveMobile;
}
bool _showAttachmentIcon() {
return isInsideThreadDetailView &&
emailSelected.hasAttachment == true &&
emailSelected.emailInThreadStatus == EmailInThreadStatus.collapsed;
}
bool _showDaysAgo(bool isResponsiveMobile) {
return isInsideThreadDetailView &&
emailSelected.emailInThreadStatus == EmailInThreadStatus.collapsed &&
!isResponsiveMobile;
}
}
@@ -4,29 +4,36 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:flutter/material.dart';
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class ReceivedTimeBuilder extends StatelessWidget {
final PresentationEmail emailSelected;
final EdgeInsetsGeometry? padding;
final bool showDaysAgo;
const ReceivedTimeBuilder({
Key? key,
required this.emailSelected,
this.padding,
this.showDaysAgo = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context);
final daysPast = getDaysPast(emailSelected.receivedAt, appLocalizations);
return Padding(
padding: padding ?? const EdgeInsetsDirectional.only(start: 16),
child: Text(
emailSelected.getReceivedAt(
Localizations.localeOf(context).toLanguageTag(),
pattern: emailSelected.receivedAt?.value.toLocal().toPatternForEmailView()
),
).toLowerCase() + daysPast,
maxLines: 1,
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap,
@@ -37,4 +44,15 @@ class ReceivedTimeBuilder extends StatelessWidget {
),
);
}
String getDaysPast(UTCDate? utcDate, AppLocalizations appLocalizations) {
if (!showDaysAgo || utcDate == null) return '';
final from = utcDate.value;
final to = DateTime.now();
if (from.isAfter(to)) return '';
final days = to.difference(from).inDays;
return appLocalizations.daysAgo(days);
}
}