TF-4075 Change attachments location

(cherry picked from commit ab23dac45d9dcf7c5e057f8e16974b3ed8205771)
This commit is contained in:
dab246
2025-10-06 19:17:25 +07:00
committed by Dat H. Pham
parent 993b05fbc2
commit 6bbebd8e67
31 changed files with 540 additions and 486 deletions
+46 -38
View File
@@ -233,6 +233,7 @@ class EmailView extends GetWidget<SingleEmailController> {
List<String>? emailAddressSender,
ScrollController? scrollController,
}) {
final isMobile = controller.responsiveUtils.isMobile(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -285,8 +286,8 @@ class EmailView extends GetWidget<SingleEmailController> {
controller.mailboxDashBoardController.mapMailboxById,
),
)),
if (!controller.responsiveUtils.isMobile(context))
const SizedBox(height: 24),
if (!isMobile)
const SizedBox(height: 16),
Obx(() => MailUnsubscribedBanner(
presentationEmail: controller.currentEmail,
emailUnsubscribe: controller.emailUnsubscribe.value
@@ -294,6 +295,8 @@ class EmailView extends GetWidget<SingleEmailController> {
Obx(() => EmailViewLoadingBarWidget(
viewState: controller.emailLoadedViewState.value
)),
if (!isMobile)
_buildAttachmentsList(context),
if (calendarEvent != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -437,46 +440,51 @@ class EmailView extends GetWidget<SingleEmailController> {
height: 5,
color: Colors.transparent,
),
Obx(() {
if (controller.attachments.isNotEmpty) {
return EmailAttachmentsWidget(
responsiveUtils: controller.responsiveUtils,
attachments: controller.attachments,
imagePaths: controller.imagePaths,
onDragStarted: controller
.mailboxDashBoardController.enableAttachmentDraggableApp,
onDragEnd: (_) {
controller
.mailboxDashBoardController
.disableAttachmentDraggableApp();
},
downloadAttachmentAction: (attachment) =>
controller.handleDownloadAttachmentAction(attachment),
viewAttachmentAction: (attachment) =>
controller.handleViewAttachmentAction(
context,
attachment,
),
onTapShowAllAttachmentFile: () => controller.openAttachmentList(
context,
controller.attachments,
),
showDownloadAllAttachmentsButton:
controller.downloadAllButtonIsEnabled(),
onTapDownloadAllButton: () =>
controller.handleDownloadAllAttachmentsAction(
'TwakeMail-${DateTime.now()}',
),
singleEmailControllerTag: tag,
);
} else {
return const SizedBox.shrink();
}
}),
if (isMobile)
_buildAttachmentsList(context),
],
);
}
Widget _buildAttachmentsList(BuildContext context) {
return Obx(() {
if (controller.attachments.isNotEmpty) {
return EmailAttachmentsWidget(
responsiveUtils: controller.responsiveUtils,
attachments: controller.attachments,
imagePaths: controller.imagePaths,
onDragStarted: controller
.mailboxDashBoardController.enableAttachmentDraggableApp,
onDragEnd: (_) {
controller
.mailboxDashBoardController
.disableAttachmentDraggableApp();
},
downloadAttachmentAction: (attachment) =>
controller.handleDownloadAttachmentAction(attachment),
viewAttachmentAction: (attachment) =>
controller.handleViewAttachmentAction(
context,
attachment,
),
onTapShowAllAttachmentFile: () => controller.openAttachmentList(
context,
controller.attachments,
),
showDownloadAllAttachmentsButton:
controller.downloadAllButtonIsEnabled(),
onTapDownloadAllButton: () =>
controller.handleDownloadAllAttachmentsAction(
'TwakeMail-${DateTime.now()}',
),
singleEmailControllerTag: tag,
);
} else {
return const SizedBox.shrink();
}
});
}
bool _validateDisplayEventActionBanner({
required BuildContext context,
required CalendarEvent event,
@@ -1,5 +1,6 @@
import 'package:core/data/constants/constant.dart';
import 'package:core/presentation/extensions/media_type_extension.dart';
import 'package:core/presentation/model/file_category.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/utils/platform_info.dart';
import 'package:model/download/download_task_id.dart';
@@ -8,7 +9,7 @@ import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_utils.dart';
extension AttachmentExtension on Attachment {
String getIcon(ImagePaths imagePaths) => type?.getIcon(imagePaths, fileName: name) ?? imagePaths.icFileEPup;
String getIcon(ImagePaths imagePaths) => type?.getIcon(imagePaths, fileName: name) ?? imagePaths.icFileDefault;
bool get isPDFFile => type?.isPDFFile(fileName: name) ?? false;
@@ -41,11 +42,9 @@ extension AttachmentExtension on Attachment {
bool get isHTMLFile => type?.isHTMLFile(fileName: name) ?? false;
bool get isImage => type?.isImageSupportedPreview(fileName: name) ?? false;
bool get isImage => type?.getFileCategory(fileName: name) == FileCategory.image;
bool get isText => (type?.isTextFile() ?? false)
|| name?.endsWith('.txt') == true
|| name?.endsWith('.md') == true;
bool get isText => type?.getFileCategory(fileName: name) == FileCategory.text;
bool get isJson => (type?.isJsonFile() ?? false)
|| name?.endsWith('.json') == true;
@@ -23,7 +23,8 @@ import 'package:tmail_ui_user/main/routes/route_utils.dart';
class EmailUtils {
static const double desktopItemMaxWidth = 260;
static const double desktopMoreButtonMaxWidth = 70;
static const double maxMobileVisibleAttachments = 3;
static const double attachmentItemSpacing = 8;
static const int maxMobileVisibleAttachments = 3;
EmailUtils._();
@@ -248,21 +249,35 @@ class EmailUtils {
if (isMobile) {
return attachments.length <= maxMobileVisibleAttachments
? attachments
: attachments.sublist(0, 3);
: attachments.sublist(0, maxMobileVisibleAttachments);
}
final displayedCount = maxWidth ~/ desktopItemMaxWidth;
if (displayedCount == attachments.length) {
final totalNeededWidth = attachments.length * desktopItemMaxWidth +
(attachments.length - 1) * attachmentItemSpacing;
if (totalNeededWidth <= maxWidth) {
return attachments;
} else {
final int possibleDisplayedCount =
((maxWidth - desktopMoreButtonMaxWidth) ~/ desktopItemMaxWidth)
.clamp(0, attachments.length);
return possibleDisplayedCount == 0
? [attachments.first]
: attachments.sublist(0, possibleDisplayedCount);
}
final availableWidth =
maxWidth - desktopMoreButtonMaxWidth - attachmentItemSpacing;
double usedWidth = 0;
int visibleCount = 0;
for (int i = 0; i < attachments.length; i++) {
final nextWidth =
desktopItemMaxWidth + (i > 0 ? attachmentItemSpacing : 0);
if (usedWidth + nextWidth <= availableWidth) {
usedWidth += nextWidth;
visibleCount++;
} else {
break;
}
}
if (visibleCount == 0) visibleCount = 1;
return attachments.sublist(0, visibleCount);
}
static String getDomainByEmailAddress(String emailAddress) {
@@ -52,79 +52,40 @@ class AttachmentsInfo extends StatelessWidget {
style: ThemeUtils.textStyleInter400.copyWith(
fontSize: 15,
height: 20 / 15,
letterSpacing: -0.24,
color: AppColor.gray99A2AD,
),
);
if (responsiveUtils.isMobile(context)) {
return Row(
children: [
iconAttachment,
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.only(start: 8, end: 3),
child: titleHeaderAttachment,
),
return Row(
children: [
iconAttachment,
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.only(start: 8, end: 3),
child: titleHeaderAttachment,
),
if (displayShowAll && numberOfAttachments > 3)
_buildShowAllButton(context),
],
);
} else {
return Row(
children: [
iconAttachment,
Expanded(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Padding(
padding: const EdgeInsetsDirectional.only(start: 8, end: 3),
child: titleHeaderAttachment,
),
),
if (displayShowAll && numberOfAttachments > 4)
_buildShowAllButton(context),
],
),
if (onTapDownloadAllButton != null)
TMailButtonWidget(
text: AppLocalizations.of(context).archiveAndDownload,
icon: imagePaths.icDownloadAll,
iconSize: 20,
iconColor: AppColor.steelGrayA540,
iconAlignment: TextDirection.rtl,
backgroundColor: Colors.transparent,
borderRadius: 5,
mainAxisSize: MainAxisSize.min,
flexibleText: true,
maxLines: 1,
maxWidth: 300,
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
textStyle: ThemeUtils.textStyleBodyBody1().copyWith(
color: AppColor.steelGray400,
),
onTapActionCallback: onTapDownloadAllButton,
),
if (onTapDownloadAllButton != null)
TMailButtonWidget(
text: AppLocalizations.of(context).archiveAndDownload,
icon: imagePaths.icDownloadAll,
iconSize: 20,
iconColor: AppColor.steelGrayA540,
iconAlignment: TextDirection.rtl,
backgroundColor: Colors.transparent,
borderRadius: 5,
mainAxisSize: MainAxisSize.min,
flexibleText: true,
maxLines: 1,
maxWidth: 300,
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
textStyle: ThemeUtils.textStyleBodyBody1().copyWith(
color: AppColor.steelGray400,
),
onTapActionCallback: onTapDownloadAllButton,
),
],
);
}
}
Widget _buildShowAllButton(BuildContext context) {
return TMailButtonWidget.fromText(
text: AppLocalizations.of(context).showAll,
backgroundColor: Colors.transparent,
textStyle: ThemeUtils.textStyleBodyBody1().copyWith(
color: AppColor.steelGray400,
),
borderRadius: 5,
maxLines: 1,
maxWidth: 120,
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
onTapActionCallback: onTapShowAllAttachmentFile,
],
);
}
}
@@ -1,10 +1,10 @@
import 'package:core/presentation/action/action_callback_define.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.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/dialog/confirm_dialog_button.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:model/email/attachment.dart';
@@ -41,7 +41,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
this.viewAttachmentAction,
this.onTapShowAllAttachmentFile,
this.showDownloadAllAttachmentsButton = false,
this.isDisplayAllAttachments = true,
this.isDisplayAllAttachments = false,
this.onTapDownloadAllButton,
this.singleEmailControllerTag,
});
@@ -68,20 +68,17 @@ class EmailAttachmentsWidget extends StatelessWidget {
final hiddenItemsCount = attachmentRecord.hiddenItemsCount;
return Padding(
padding: const EdgeInsetsDirectional.only(top: 12, bottom: 24),
padding: const EdgeInsetsDirectional.only(
start: 16,
end: 16,
bottom: 24,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
attachmentHeader,
Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 12),
child: attachmentHeader,
),
Padding(
padding: const EdgeInsetsDirectional.only(
start: 12,
end: 12,
top: 4,
),
padding: const EdgeInsetsDirectional.only(top: 4),
child: Column(
children: displayedAttachments.map((attachment) {
return AttachmentItemWidget(
@@ -95,189 +92,106 @@ class EmailAttachmentsWidget extends StatelessWidget {
}).toList(),
),
),
const SizedBox(height: 12),
if (hiddenItemsCount > 0 && showDownloadAllAttachmentsButton)
Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 8),
child: Row(
children: [
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).moreAttachments(
hiddenItemsCount,
),
backgroundColor: Colors.transparent,
borderRadius: 5,
maxWidth: 120,
maxLines: 1,
textStyle: ThemeUtils.textStyleM3TitleSmall,
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 5,
),
onTapActionCallback: onTapShowAllAttachmentFile,
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: TMailButtonWidget(
text: AppLocalizations
.of(context)
.archiveAndDownload,
icon: imagePaths.icDownloadAll,
iconSize: 20,
iconColor: AppColor.steelGrayA540,
iconAlignment: TextDirection.rtl,
backgroundColor: Colors.transparent,
borderRadius: 5,
mainAxisSize: MainAxisSize.min,
maxLines: 1,
flexibleText: true,
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 5,
),
textStyle: ThemeUtils.textStyleBodyBody1()
.copyWith(color: AppColor.steelGray400),
onTapActionCallback: onTapDownloadAllButton,
),
),
],
),
),
],
),
)
else if (hiddenItemsCount > 0)
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).moreAttachments(
hiddenItemsCount,
),
backgroundColor: Colors.transparent,
borderRadius: 5,
maxLines: 1,
textStyle: ThemeUtils.textStyleM3TitleSmall,
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 5,
),
margin: const EdgeInsetsDirectional.symmetric(horizontal: 8),
onTapActionCallback: onTapShowAllAttachmentFile,
)
else if (showDownloadAllAttachmentsButton)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: TMailButtonWidget(
text: AppLocalizations.of(context).archiveAndDownload,
icon: imagePaths.icDownloadAll,
iconSize: 20,
iconColor: AppColor.steelGrayA540,
iconAlignment: TextDirection.rtl,
backgroundColor: Colors.transparent,
borderRadius: 5,
mainAxisSize: MainAxisSize.min,
maxLines: 1,
flexibleText: true,
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 5,
),
margin: const EdgeInsetsDirectional.symmetric(
horizontal: 8,
),
textStyle: ThemeUtils.textStyleBodyBody1().copyWith(
color: AppColor.steelGray400,
),
onTapActionCallback: onTapDownloadAllButton,
),
),
],
const SizedBox(height: 8),
if (hiddenItemsCount > 0)
SizedBox(
height: 36,
width: double.infinity,
child: ConfirmDialogButton(
label: AppLocalizations.of(context).moreAttachments(
hiddenItemsCount,
),
backgroundColor: Theme.of(context).colorScheme.outline.withValues(
alpha: 0.08,
),
textStyle: ThemeUtils.textStyleM3TitleSmall,
radius: 5,
onTapAction: onTapShowAllAttachmentFile,
),
),
],
),
);
} else {
return Padding(
padding: const EdgeInsetsDirectional.only(
start: 16,
end: 16,
top: 16,
bottom: 28,
start: 8,
end: 8,
top: 12,
bottom: 16,
),
child: LayoutBuilder(
builder: (context, constraints) {
final attachmentRecord = _getDisplayedAndHiddenAttachment(
context,
constraints.maxWidth,
);
final displayedAttachments = attachmentRecord.displayedAttachments;
final hiddenItemsCount = attachmentRecord.hiddenItemsCount;
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 12),
child: attachmentHeader,
),
Padding(
padding: const EdgeInsetsDirectional.only(
start: 12,
end: 12,
top: 12,
),
child: LayoutBuilder(
builder: (context, constraints) {
final attachmentRecord = _getDisplayedAndHiddenAttachment(
context,
constraints.maxWidth,
);
final displayedAttachments = attachmentRecord.displayedAttachments;
final hiddenItemsCount = attachmentRecord.hiddenItemsCount;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 12),
child: attachmentHeader,
),
Padding(
padding: const EdgeInsetsDirectional.only(
start: 12,
end: 12,
top: 12,
),
child: Wrap(
spacing: 8,
runSpacing: isDisplayAllAttachments ? 8 : 0,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...displayedAttachments.map((attachment) {
if (responsiveUtils.isDesktop(context)) {
return DraggableAttachmentItemWidget(
attachment: attachment,
imagePaths: imagePaths,
width: EmailUtils.desktopItemMaxWidth,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
} else {
return AttachmentItemWidget(
attachment: attachment,
imagePaths: imagePaths,
width: EmailUtils.desktopItemMaxWidth,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
}
}).toList(),
if (hiddenItemsCount > 0)
TMailButtonWidget.fromText(
text: '+ $hiddenItemsCount',
backgroundColor: Colors.transparent,
borderRadius: 5,
maxWidth: EmailUtils.desktopMoreButtonMaxWidth,
maxLines: 1,
textStyle: ThemeUtils.textStyleM3TitleSmall,
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 5,
return Wrap(
spacing: EmailUtils.attachmentItemSpacing,
runSpacing: isDisplayAllAttachments
? EmailUtils.attachmentItemSpacing
: 0,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...displayedAttachments.map((attachment) {
if (PlatformInfo.isWeb &&
responsiveUtils.isDesktop(context)) {
return DraggableAttachmentItemWidget(
attachment: attachment,
imagePaths: imagePaths,
width: EmailUtils.desktopItemMaxWidth,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
} else {
return AttachmentItemWidget(
attachment: attachment,
imagePaths: imagePaths,
width: EmailUtils.desktopItemMaxWidth,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
}
}).toList(),
if (hiddenItemsCount > 0)
SizedBox(
height: 36,
child: ConfirmDialogButton(
label: '+$hiddenItemsCount',
backgroundColor: Theme.of(context).colorScheme.outline.withValues(
alpha: 0.08,
),
textStyle: ThemeUtils.textStyleM3TitleSmall,
radius: 5,
onTapAction: onTapShowAllAttachmentFile,
),
),
onTapActionCallback: onTapShowAllAttachmentFile,
),
],
),
),
],
);
}
],
);
}
),
),
],
),
);
}
@@ -182,6 +182,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
fontSize: 15
),
backgroundColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
onTapActionCallback: () => setState(() => _isDisplayAll = false),
)
]
@@ -74,10 +74,10 @@ class UploadFileState with EquatableMixin {
if (mediaType == null && file != null) {
mediaType = MediaType.parse(file!.mimeType);
}
return mediaType?.getIcon(imagePaths, fileName: fileName) ?? imagePaths.icFileEPup;
return mediaType?.getIcon(imagePaths, fileName: fileName) ?? imagePaths.icFileDefault;
} catch (e) {
logError('UploadFileState::getIcon: Exception: $e');
return imagePaths.icFileEPup;
return imagePaths.icFileDefault;
}
}