From 5c6a995107f51c00204d74ab4f076c42a0a483e5 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 10 Mar 2025 12:23:35 +0700 Subject: [PATCH] TF-3472 Prefer at least two attachment for mobile Signed-off-by: dab246 --- .../styles/email_attachments_styles.dart | 6 + .../email/presentation/utils/email_utils.dart | 39 +++++ .../widgets/email_attachments_widget.dart | 52 +++--- .../get_attachment_displayed_test.dart | 160 ++++++++++++++++++ 4 files changed, 234 insertions(+), 23 deletions(-) create mode 100644 test/features/email/presentation/get_attachment_displayed_test.dart diff --git a/lib/features/email/presentation/styles/email_attachments_styles.dart b/lib/features/email/presentation/styles/email_attachments_styles.dart index 843b38ac5..6d063b938 100644 --- a/lib/features/email/presentation/styles/email_attachments_styles.dart +++ b/lib/features/email/presentation/styles/email_attachments_styles.dart @@ -3,12 +3,16 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:flutter/material.dart'; class EmailAttachmentsStyles { + EmailAttachmentsStyles._(); + static const double headerTextSize = 15; static const double headerIconSize = 20; static const double headerSpace = 8; static const double marginHeader = 6; static const double buttonTextSize = 16; static const double buttonMoreAttachmentsTextSize = 14; + static const double buttonMoreMaxWidth = 120; + static const double buttonDownloadAllMaxWidth = 200; static const double buttonBorderRadius = 8; static const double listSpace = 8; @@ -23,6 +27,8 @@ class EmailAttachmentsStyles { static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.only(start: 16, end: 16, bottom: 12); static const EdgeInsetsGeometry buttonPadding = EdgeInsets.symmetric(vertical: 8, horizontal: 12); + static const EdgeInsetsGeometry mobileButtonPadding = EdgeInsets.symmetric(vertical: 8, horizontal: 3); static const EdgeInsetsGeometry moreButtonMargin = EdgeInsetsDirectional.only(bottom: 2, start: 8); + static const EdgeInsetsGeometry mobileMoreButtonMargin = EdgeInsetsDirectional.only(end: 8); static const attachmentsInfoPadding = EdgeInsetsDirectional.only(end: 8); } \ No newline at end of file diff --git a/lib/features/email/presentation/utils/email_utils.dart b/lib/features/email/presentation/utils/email_utils.dart index 7b67cbcbb..c1209b97e 100644 --- a/lib/features/email/presentation/utils/email_utils.dart +++ b/lib/features/email/presentation/utils/email_utils.dart @@ -1,8 +1,10 @@ import 'package:collection/collection.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/mail/mail_address.dart'; +import 'package:flutter/material.dart'; import 'package:get/get_utils/src/get_utils/get_utils.dart'; import 'package:dartz/dartz.dart'; import 'package:http_parser/http_parser.dart'; @@ -17,11 +19,14 @@ import 'package:model/email/attachment.dart'; import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/attachment/attachment_item_widget_style.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/email_attachments_styles.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; class EmailUtils { + EmailUtils._(); static Properties getPropertiesForEmailGetMethod(Session session, AccountId accountId) { if (CapabilityIdentifier.jamesCalendarEvent.isSupported(session, accountId)) { @@ -225,4 +230,38 @@ class EmailUtils { recipientRecord.ccMailAddresses.isNotEmpty || recipientRecord.bccMailAddresses.isNotEmpty; } + + static List getAttachmentDisplayed({ + required BuildContext context, + required double maxWidth, + required bool platformIsMobile, + required List attachments, + required ResponsiveUtils responsiveUtils, + }) { + if (attachments.isEmpty) return []; + + final bool isMobile = responsiveUtils.isMobile(context); + log('EmailUtils::getAttachmentDisplayed:isMobile = $isMobile:'); + + if (isMobile) { + return attachments.length < 3 ? attachments : attachments.sublist(0, 2); + } + + final double maxWidthItem = AttachmentItemWidgetStyle.getMaxWidthItem( + platformIsMobile: platformIsMobile, + responsiveIsMobile: isMobile, + responsiveIsTablet: responsiveUtils.isTablet(context), + responsiveIsTabletLarge: responsiveUtils.isTabletLarge(context), + ); + log('EmailUtils::getAttachmentDisplayed:maxWidthItem = $maxWidthItem:'); + + final int possibleDisplayedCount = + ((maxWidth - EmailAttachmentsStyles.buttonMoreMaxWidth) ~/ maxWidthItem) + .clamp(0, attachments.length); + log('EmailUtils::getAttachmentDisplayed:possibleDisplayedCount = $possibleDisplayedCount:'); + + return possibleDisplayedCount == 0 + ? [attachments.first] + : attachments.sublist(0, possibleDisplayedCount); + } } \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/email_attachments_widget.dart b/lib/features/email/presentation/widgets/email_attachments_widget.dart index a2f8dcc38..a6ec2e171 100644 --- a/lib/features/email/presentation/widgets/email_attachments_widget.dart +++ b/lib/features/email/presentation/widgets/email_attachments_widget.dart @@ -7,8 +7,8 @@ import 'package:filesize/filesize.dart'; import 'package:flutter/material.dart'; import 'package:model/email/attachment.dart'; import 'package:model/extensions/list_attachment_extension.dart'; -import 'package:tmail_ui_user/features/email/presentation/styles/attachment/attachment_item_widget_style.dart'; import 'package:tmail_ui_user/features/email/presentation/styles/email_attachments_styles.dart'; +import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_item_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachments_info.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/draggable_attachment_item_widget.dart'; @@ -44,19 +44,22 @@ class EmailAttachmentsWidget extends StatelessWidget { Widget _buildMoreAttachmentButton( BuildContext context, int hideItemsCount, { - EdgeInsetsGeometry padding = EdgeInsets.zero, + EdgeInsetsGeometry? padding, + EdgeInsetsGeometry? margin, }) { return TMailButtonWidget( text: AppLocalizations.of(context).moreAttachments(hideItemsCount), backgroundColor: Colors.transparent, borderRadius: EmailAttachmentsStyles.buttonBorderRadius, padding: padding, - margin: EmailAttachmentsStyles.moreButtonMargin, + maxWidth: EmailAttachmentsStyles.buttonMoreMaxWidth, + margin: margin, textStyle: const TextStyle( fontSize: EmailAttachmentsStyles.buttonMoreAttachmentsTextSize, color: EmailAttachmentsStyles.buttonMoreAttachmentsTextColor, fontWeight: EmailAttachmentsStyles.buttonMoreAttachmentsFontWeight, ), + maxLines: 1, onTapActionCallback: onTapShowAllAttachmentFile, ); } @@ -64,12 +67,19 @@ class EmailAttachmentsWidget extends StatelessWidget { @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { - final attachmentDisplayed = _getAttachmentDisplayed( - context, - constraints.maxWidth + final attachmentDisplayed = EmailUtils.getAttachmentDisplayed( + context: context, + maxWidth: constraints.maxWidth - EmailAttachmentsStyles.padding.horizontal - - EmailAttachmentsStyles.listSpace * 2); + - EmailAttachmentsStyles.listSpace * 2, + platformIsMobile: PlatformInfo.isMobile, + attachments: attachments, + responsiveUtils: responsiveUtils, + ); int hideItemsCount = attachments.length - attachmentDisplayed.length; + if (hideItemsCount > 999) { + hideItemsCount = 999; + } return Padding( padding: EmailAttachmentsStyles.padding, child: Column( @@ -118,6 +128,7 @@ class EmailAttachmentsWidget extends StatelessWidget { context, hideItemsCount, padding: EmailAttachmentsStyles.buttonPadding, + margin: EmailAttachmentsStyles.moreButtonMargin, ), ] ), @@ -126,7 +137,12 @@ class EmailAttachmentsWidget extends StatelessWidget { Row( children: [ if (hideItemsCount > 0) - _buildMoreAttachmentButton(context, hideItemsCount), + _buildMoreAttachmentButton( + context, + hideItemsCount, + padding: EmailAttachmentsStyles.mobileButtonPadding, + margin: EmailAttachmentsStyles.mobileMoreButtonMargin, + ), const Spacer(), if (showDownloadAllAttachmentsButton) TMailButtonWidget( @@ -135,12 +151,16 @@ class EmailAttachmentsWidget extends StatelessWidget { iconAlignment: TextDirection.rtl, backgroundColor: Colors.transparent, borderRadius: EmailAttachmentsStyles.buttonBorderRadius, - padding: EmailAttachmentsStyles.buttonPadding, + padding: EmailAttachmentsStyles.mobileButtonPadding, textStyle: const TextStyle( fontSize: EmailAttachmentsStyles.buttonTextSize, color: EmailAttachmentsStyles.buttonTextColor, fontWeight: EmailAttachmentsStyles.buttonFontWeight ), + maxWidth: EmailAttachmentsStyles.buttonDownloadAllMaxWidth, + maxLines: 1, + mainAxisSize: MainAxisSize.min, + flexibleText: true, onTapActionCallback: onTapDownloadAllButton, ), ] @@ -150,18 +170,4 @@ class EmailAttachmentsWidget extends StatelessWidget { ); }); } - - List _getAttachmentDisplayed(BuildContext context, double maxWidth) { - const moreAttachmentButtonWidth = 120; // Asumming 999 more items - final maxWidthItem = AttachmentItemWidgetStyle.getMaxWidthItem( - platformIsMobile: PlatformInfo.isMobile, - responsiveIsMobile: responsiveUtils.isMobile(context), - responsiveIsTablet: responsiveUtils.isTablet(context), - responsiveIsTabletLarge: responsiveUtils.isTabletLarge(context), - ); - final possibleNumberOfDisplayedAttachments = (maxWidth - moreAttachmentButtonWidth) ~/ maxWidthItem; - return possibleNumberOfDisplayedAttachments >= attachments.length - ? attachments - : attachments.sublist(0, possibleNumberOfDisplayedAttachments); - } } diff --git a/test/features/email/presentation/get_attachment_displayed_test.dart b/test/features/email/presentation/get_attachment_displayed_test.dart new file mode 100644 index 000000000..0f0e9a322 --- /dev/null +++ b/test/features/email/presentation/get_attachment_displayed_test.dart @@ -0,0 +1,160 @@ +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/email/attachment.dart'; +import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; + +import 'get_attachment_displayed_test.mocks.dart'; + +@GenerateMocks([BuildContext, ResponsiveUtils]) +void main() { + late MockBuildContext mockContext; + late MockResponsiveUtils mockResponsiveUtils; + late List attachments; + + setUp(() { + mockContext = MockBuildContext(); + mockResponsiveUtils = MockResponsiveUtils(); + attachments = [ + Attachment(name: 'file1.pdf'), + Attachment(name: 'file2.jpg'), + Attachment(name: 'file3.png'), + ]; + }); + + test('should return empty list when attachments are empty', () { + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 300.0, + platformIsMobile: true, + attachments: [], + ); + expect(result, []); + }); + + test('should return all attachments when mobile and attachments < 3', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(true); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 300.0, + platformIsMobile: true, + attachments: attachments.sublist(0, 2), + ); + expect(result.length, 2); + expect(result, attachments.sublist(0, 2)); + }); + + test('should return first 2 attachments when mobile and attachments >= 3', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(true); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 300.0, + platformIsMobile: true, + attachments: attachments, + ); + expect(result.length, 2); + expect(result, attachments.sublist(0, 2)); + }); + + test('should return sublist based on maxWidthTabletLarge when responsiveIsTabletLarge is true', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTablet(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTabletLarge(mockContext)).thenReturn(true); + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 360.0, + platformIsMobile: false, + attachments: attachments, + ); + expect(result.length, 1); + expect(result, attachments.sublist(0, 1)); + }); + + test('should return sublist based on maxWidthTablet when responsiveIsTablet is true', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTablet(mockContext)).thenReturn(true); + when(mockResponsiveUtils.isTabletLarge(mockContext)).thenReturn(false); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 320.0, + platformIsMobile: false, + attachments: attachments, + ); + expect(result.length, 1); + expect(result, attachments.sublist(0, 1)); + }); + + test('should return sublist based on maxWidthMobile when not tablet or tabletLarge', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTablet(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTabletLarge(mockContext)).thenReturn(false); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 280.0, + platformIsMobile: false, + attachments: attachments, + ); + expect(result.length, 1); + expect(result, attachments.sublist(0, 1)); + }); + + test('should return sublist based on maxWidthMobile when platformIsMobile and responsiveIsMobile are true', () { + when(mockResponsiveUtils.isTablet(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTabletLarge(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(true); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 280.0, + platformIsMobile: true, + attachments: attachments, + ); + expect(result.length, 2); + expect(result, attachments.sublist(0, 2)); + }); + + test('should return sublist based on maxWidthTablet when platformIsMobile is true and responsiveIsMobile is false', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTablet(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTabletLarge(mockContext)).thenReturn(false); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 320.0, + platformIsMobile: true, + attachments: attachments, + ); + expect(result.length, 1); + expect(result, attachments.sublist(0, 1)); + }); + + test('should return first attachment when possibleDisplayedCount is 0', () { + when(mockResponsiveUtils.isMobile(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTablet(mockContext)).thenReturn(false); + when(mockResponsiveUtils.isTabletLarge(mockContext)).thenReturn(false); + + final result = EmailUtils.getAttachmentDisplayed( + context: mockContext, + responsiveUtils: mockResponsiveUtils, + maxWidth: 100.0, + platformIsMobile: false, + attachments: attachments, + ); + expect(result.length, 1); + expect(result.first, attachments.first); + }); +} \ No newline at end of file