From 36a90fbc63b93ea725635603b82eb59898c7b64b Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 12 Oct 2022 17:02:10 +0700 Subject: [PATCH] TF-985 Apply new design for list attachment in EmailView --- assets/images/ic_download_attachment.svg | 5 + .../extensions/color_extension.dart | 8 +- .../presentation/resources/image_paths.dart | 1 + .../composer/presentation/composer_view.dart | 2 +- .../presentation/composer_view_web.dart | 2 +- .../email/presentation/email_controller.dart | 2 + .../email/presentation/email_view.dart | 198 ++++++++-------- .../widgets/attachment_file_tile_builder.dart | 215 +++++++----------- .../bottom_bar_mail_widget_builder.dart | 8 +- lib/l10n/intl_messages.arb | 16 +- lib/main/localizations/app_localizations.dart | 12 +- 11 files changed, 214 insertions(+), 255 deletions(-) create mode 100644 assets/images/ic_download_attachment.svg diff --git a/assets/images/ic_download_attachment.svg b/assets/images/ic_download_attachment.svg new file mode 100644 index 000000000..91b55b9d6 --- /dev/null +++ b/assets/images/ic_download_attachment.svg @@ -0,0 +1,5 @@ + + + diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 230e9a979..b6c3a3fb4 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -46,9 +46,9 @@ extension AppColor on Color { static const bgMailboxListMail = Color(0xFFFBFBFF); static const bgMessenger = Color(0xFFF2F2F5); static const textButtonColor = Color(0xFF182952); - static const attachmentFileBorderColor = Color(0xFFEAEAEA); - static const attachmentFileNameColor = Color(0xFF182952); - static const attachmentFileSizeColor = Color(0xFF7E869B); + static const attachmentFileBorderColor = Color(0x1F000000); + static const attachmentFileNameColor = Color(0xFF000000); + static const attachmentFileSizeColor = Color(0xFF818C99); static const avatarColor = Color(0xFFF8F8F8); static const avatarTextColor = Color(0xFF3840F7); static const sentTimeTextColorUnRead = Color(0xFF182952); @@ -152,6 +152,8 @@ extension AppColor on Color { static const colorShadowLayerTop = Color(0x1F000000); static const colorDividerHorizontal = Color(0x1F000000); static const colorEmailAddressFull = Color(0xFF818C99); + static const colorTitleHeaderAttachment = Color(0xFF818C99); + static const colorAttachmentIcon = Color(0xFFAEB7C2); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index c6b586381..bb2adf646 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -162,6 +162,7 @@ class ImagePaths { String get icAddEmailForward => _getImagePath('ic_add_email_forwards.svg'); String get icChevronDownOutline => _getImagePath('ic_chevron_down_outline.svg'); String get icUndo => _getImagePath('ic_undo.svg'); + String get icDownloadAttachment => _getImagePath('ic_download_attachment.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 6502a0108..30563db71 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -708,7 +708,7 @@ class ComposerView extends GetWidget child: Text( expandModeAttachment == ExpandMode.EXPAND ? AppLocalizations.of(context).hide - : '${AppLocalizations.of(context).show_all} (${uploadFilesState.length})', + : '${AppLocalizations.of(context).showAll} (${uploadFilesState.length})', style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 12, color: AppColor.colorTextButton)), onPressed: () => controller.toggleDisplayAttachments() ) diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 3cc3ef723..8145721fc 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -704,7 +704,7 @@ class ComposerView extends GetWidget child: Text( expandModeAttachment == ExpandMode.EXPAND ? AppLocalizations.of(context).hide - : '${AppLocalizations.of(context).show_all} (${uploadFilesState.length})', + : '${AppLocalizations.of(context).showAll} (${uploadFilesState.length})', style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 12, color: AppColor.colorTextButton)), onPressed: () => controller.toggleDisplayAttachments() ) diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index a5697439b..1f40e3227 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -84,6 +84,8 @@ class EmailController extends BaseController with AppLoaderMixin { bool get isDisplayFullEmailAddress => emailAddressExpandMode.value == ExpandMode.EXPAND; + bool get isDisplayFullAttachments => attachmentsExpandMode.value == ExpandMode.EXPAND; + EmailController( this._getEmailContentInteractor, this._markAsEmailReadInteractor, diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 6c42eb236..d91398aeb 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -1,7 +1,6 @@ import 'package:core/core.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -579,115 +578,112 @@ class EmailView extends GetWidget with NetworkConnectionMixin { }); } - int _getAttachmentLimitDisplayed(BuildContext context) { - if (responsiveUtils.isMobile(context) - || responsiveUtils.isLandscapeMobile(context)) { - return 2; - } else if (responsiveUtils.isTablet(context) || - responsiveUtils.isLandscapeTablet(context)) { - return 3; - } else { - return 4; - } - } - Widget _buildAttachmentsBody(BuildContext context, List attachments) { - final attachmentLimitDisplayed = _getAttachmentLimitDisplayed(context); - final countAttachments = _getListAttachmentsSize( - context, - controller.attachmentsExpandMode.value, - attachments, - attachmentLimitDisplayed); - final isExpand = controller.attachmentsExpandMode.value == ExpandMode.EXPAND - && attachments.length > attachmentLimitDisplayed; - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (isExpand) - Padding( - padding: EdgeInsets.zero, - child: _buildAttachmentsHeader(context, attachments)), - GridView.builder( - key: const Key('list_attachment'), - primary: false, - shrinkWrap: true, - padding: EdgeInsets.only(top: isExpand ? 0 : 16, bottom: 16), - itemCount: countAttachments, - gridDelegate: SliverGridDelegateFixedHeight( - height: 60, - crossAxisCount: attachmentLimitDisplayed, - crossAxisSpacing: 16.0, - mainAxisSpacing: 8.0), - itemBuilder: (context, index) => - (AttachmentFileTileBuilder( - imagePaths, - attachments[index], - attachments.length, - attachmentLimitDisplayed) - ..setExpandMode((countAttachments - 1 == index) ? controller.attachmentsExpandMode.value : null) - ..onExpandAttachmentActionClick(() => controller.toggleDisplayAttachmentsAction()) - ..onDownloadAttachmentFileActionClick((attachment) { - if (kIsWeb) { - controller.downloadAttachmentForWeb(context, attachment); - } else { - controller.exportAttachment(context, attachment); - } - })) - .build()) - ], + return Container( + color: Colors.white, + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12, top: 10), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildAttachmentsHeader(context, attachments), + _buildAttachmentsList(context, attachments, controller.isDisplayFullAttachments) + ], + ), ); } - int _getListAttachmentsSize( - BuildContext context, - ExpandMode expandMode, - List attachments, - int limitDisplayAttachment - ) { - if (attachments.length > limitDisplayAttachment) { - return expandMode == ExpandMode.EXPAND - ? attachments.length - : attachments.sublist(0, limitDisplayAttachment).length; - } else { - return attachments.length; - } - } - Widget _buildAttachmentsHeader(BuildContext context, List attachments) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context).count_attachment(attachments.length), - style: const TextStyle(fontSize: 12, color: AppColor.baseTextColor)), - Padding( - padding: const EdgeInsets.only(left: 8), - child: Text( - '(${filesize(attachments.totalSize(), 1)})', - style: const TextStyle(fontSize: 12, color: AppColor.nameUserColor, fontWeight: FontWeight.w500))) - ], - ), - if (attachments.length > 2) - Padding( - padding: const EdgeInsets.only(left: 8), - child: IconButton( - icon: SvgPicture.asset(imagePaths.icExpandAttachment, - width: 20, - height: 20, - color: AppColor.primaryColor, - fit: BoxFit.fill), - onPressed: () => controller.toggleDisplayAttachmentsAction() + return Container( + color: Colors.white, + child: Row( + children: [ + Expanded(child: Container( + margin: const EdgeInsets.symmetric(vertical: 4), + child: Row(children: [ + SvgPicture.asset(imagePaths.icAttachment, + width: 20, + height: 20, + color: AppColor.colorAttachmentIcon, + fit: BoxFit.fill), + const SizedBox(width: 5), + Expanded(child: Text( + AppLocalizations.of(context).titleHeaderAttachment( + attachments.length, + filesize(attachments.totalSize(), 1)), + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + color: AppColor.colorTitleHeaderAttachment))) + ]) + )), + if (attachments.length > 2) + Obx(() => Material( + color: Colors.transparent, + child: InkWell( + onTap: controller.toggleDisplayAttachmentsAction, + customBorder: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: Text( + controller.isDisplayFullAttachments + ? AppLocalizations.of(context).hide + : AppLocalizations.of(context).showAll, + style: const TextStyle( + fontSize: 13, + color: AppColor.colorTextButton, + fontWeight: FontWeight.normal)), + ), + ), )) - ], + ], + ), ); } + Widget _buildAttachmentsList( + BuildContext context, + List attachments, + bool isDisplayAll + ) { + return LayoutBuilder(builder: (context, constraints) { + if (isDisplayAll) { + return Wrap( + runSpacing: 12, + children: attachments + .map((attachment) => AttachmentFileTileBuilder( + attachment, + onDownloadAttachmentFileActionClick: (attachment) { + if (BuildUtils.isWeb) { + controller.downloadAttachmentForWeb(context, attachment); + } else { + controller.exportAttachment(context, attachment); + } + })) + .toList()); + } else { + return Container( + height: 60, + color: Colors.transparent, + child: ListView.builder( + key: const Key('list_attachment_minimize_in_email'), + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + itemCount: attachments.length, + itemBuilder: (context, index) => AttachmentFileTileBuilder( + attachments[index], + onDownloadAttachmentFileActionClick: (attachment) { + if (BuildUtils.isWeb) { + controller.downloadAttachmentForWeb(context, attachment); + } else { + controller.exportAttachment(context, attachment); + } + }) + ) + ); + } + }); + } + Widget _buildEmailContent(BuildContext context, BoxConstraints constraints) { return Obx(() { if (controller.emailContents.isNotEmpty) { diff --git a/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart b/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart index 7829d8e5b..40fd78531 100644 --- a/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart +++ b/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart @@ -3,157 +3,102 @@ import 'package:core/core.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; typedef OnDownloadAttachmentFileActionClick = void Function(Attachment attachment); typedef OnExpandAttachmentActionClick = void Function(); -class AttachmentFileTileBuilder { +class AttachmentFileTileBuilder extends StatelessWidget{ - final ImagePaths _imagePaths; final Attachment _attachment; - final int _attachmentSize; - final int _limitDisplayAttachment; - ExpandMode? _expandMode; + final OnDownloadAttachmentFileActionClick? onDownloadAttachmentFileActionClick; - OnDownloadAttachmentFileActionClick? _onDownloadAttachmentFileActionClick; - OnExpandAttachmentActionClick? _onExpandAttachmentActionClick; + const AttachmentFileTileBuilder( + this._attachment, { + Key? key, + this.onDownloadAttachmentFileActionClick, + }) : super(key: key); - Widget? buttonAction; - double? heightItem; + @override + Widget build(BuildContext context) { + final imagePaths = Get.find(); + final responsiveUtils = Get.find(); - AttachmentFileTileBuilder( - this._imagePaths, - this._attachment, - this._attachmentSize, - this._limitDisplayAttachment, - ); - - void onDownloadAttachmentFileActionClick(OnDownloadAttachmentFileActionClick onDownloadAttachmentFileActionClick) { - _onDownloadAttachmentFileActionClick = onDownloadAttachmentFileActionClick; - } - - void onExpandAttachmentActionClick(OnExpandAttachmentActionClick onExpandAttachmentActionClick) { - _onExpandAttachmentActionClick = onExpandAttachmentActionClick; - } - - void setExpandMode(ExpandMode? expandMode) { - _expandMode = expandMode; - } - - void addButtonAction(Widget action) { - buttonAction = action; - } - - void height(double height) { - heightItem = height; - } - - Widget build() { - return Theme( - data: ThemeData( - splashColor: Colors.transparent , - highlightColor: Colors.transparent), - child: Container( - key: const Key('attach_file_tile'), - alignment: Alignment.center, - margin: EdgeInsets.only(top:heightItem != null ? 8 : 0), - height: heightItem, - padding: EdgeInsets.zero, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColor.attachmentFileBorderColor), - color: Colors.white), - child: MediaQuery( - data: const MediaQueryData(padding: EdgeInsets.zero), - child: Stack( - alignment: AlignmentDirectional.bottomEnd, - children: [ - ListTile( - contentPadding: EdgeInsets.zero, - focusColor: AppColor.primaryColor, - hoverColor: AppColor.primaryColor, - onTap: () { - if (_onDownloadAttachmentFileActionClick != null) { - _onDownloadAttachmentFileActionClick!(_attachment); - }}, - leading: Padding( - padding: const EdgeInsets.only(left: 8, bottom: BuildUtils.isWeb ? 6 : 14), - child: SvgPicture.asset( - _attachment.getIcon(_imagePaths), - width: 40, - height: 40, - fit: BoxFit.fill), + return Padding( + padding: const EdgeInsets.only(right: 12), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onDownloadAttachmentFileActionClick?.call(_attachment), + customBorder: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColor.attachmentFileBorderColor), + color: Colors.transparent), + width: responsiveUtils.isMobile(context) ? 224 : 250, + height: 60, + child: Stack( + children: [ + Positioned.fill( + child: Row(children: [ + SvgPicture.asset( + _attachment.getIcon(imagePaths), + width: 44, + height: 44, + fit: BoxFit.fill), + const SizedBox(width: 8), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _attachment.name ?? '', + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + fontSize: 14, + color: AppColor.attachmentFileNameColor, + fontWeight: FontWeight.normal), + ), + const SizedBox(height: 4), + Text( + filesize(_attachment.size?.value), + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + fontSize: 12, + color: AppColor.attachmentFileSizeColor, + fontWeight: FontWeight.normal), + ) + ] + )) + ]), ), - title: Transform( - transform: Matrix4.translationValues( - BuildUtils.isWeb ? 0.0 : -8.0, - BuildUtils.isWeb ? -8.0 : -10.0, - 0.0), - child: Text( - _attachment.name ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: AppColor.attachmentFileNameColor, fontWeight: FontWeight.w500), - )), - subtitle: _attachment.size != null && _attachment.size?.value != 0 - ? Transform( - transform: Matrix4.translationValues( - BuildUtils.isWeb ? 0.0 : -8.0, - BuildUtils.isWeb ? -8.0 : -10.0, - 0.0), - child: Text( - filesize(_attachment.size?.value), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: AppColor.attachmentFileSizeColor))) - : null, - trailing: buttonAction != null - ? Transform( - transform: Matrix4.translationValues(-5.0, heightItem != null ? -6.0 : 0.0, 0.0), - child: buttonAction) - : null - ), - if (buttonAction == null) - Transform( - transform: Matrix4.translationValues(5.0, 5.0, 0.0), - child: IconButton( - icon: SvgPicture.asset( - _imagePaths.icDownload, + Align( + alignment: Alignment.bottomRight, + child: Material( + color: Colors.transparent, + child: InkWell( + customBorder: const CircleBorder(), + child: SvgPicture.asset( + imagePaths.icDownloadAttachment, width: 24, height: 24, color: AppColor.primaryColor, fit: BoxFit.fill), - onPressed: () { - if (_onDownloadAttachmentFileActionClick != null) { - _onDownloadAttachmentFileActionClick!(_attachment); - } - } - )), - if (_attachmentSize > _limitDisplayAttachment && _expandMode == ExpandMode.COLLAPSE) _buildItemBackground() - ] - ) - ) - ) - ); - } - - Widget _buildItemBackground() { - return GestureDetector( - onTap: () { - if (_onExpandAttachmentActionClick != null) { - _onExpandAttachmentActionClick!(); - } - }, - child: Stack( - alignment: AlignmentDirectional.center, - children: [ - Container(color: AppColor.backgroundCountAttachment), - Text( - '+${_attachmentSize - _limitDisplayAttachment + 1}', - style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold)) - ], + onTap: () => onDownloadAttachmentFileActionClick?.call(_attachment) + ), + ), + ), + ] + ) + ), + ), ), ); } diff --git a/lib/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart b/lib/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart index 421149a89..535a80ec7 100644 --- a/lib/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart +++ b/lib/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart @@ -28,7 +28,13 @@ class BottomBarMailWidgetBuilder extends StatelessWidget { return Container( alignment: Alignment.center, - color: Colors.white, + decoration: responsiveUtils.isWebDesktop(context) + ? const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(20), + bottomLeft: Radius.circular(20))) + : const BoxDecoration(color: Colors.white), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index a2ecbfa27..8354f3183 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-10-12T11:51:14.324607", + "@@last_modified": "2022-10-12T16:44:35.025767", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -358,14 +358,16 @@ "placeholders_order": [], "placeholders": {} }, - "count_attachment": "{count} attachments", - "@count_attachment": { + "titleHeaderAttachment": "{count} Attachments ({totalSize}):", + "@titleHeaderAttachment": { "type": "text", "placeholders_order": [ - "count" + "count", + "totalSize" ], "placeholders": { - "count": {} + "count": {}, + "totalSize": {} } }, "attach_file_prepare_text": "Preparing to attach file...", @@ -792,8 +794,8 @@ "placeholders_order": [], "placeholders": {} }, - "show_all": "Show all", - "@show_all": { + "showAll": "Show all", + "@showAll": { "type": "text", "placeholders_order": [], "placeholders": {} diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 7759b15b9..c9285e6fe 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -374,11 +374,11 @@ class AppLocalizations { ); } - String count_attachment(int count) { + String titleHeaderAttachment(int count, String totalSize) { return Intl.message( - '$count attachments', - name: 'count_attachment', - args: [count] + '$count Attachments ($totalSize):', + name: 'titleHeaderAttachment', + args: [count, totalSize] ); } @@ -821,10 +821,10 @@ class AppLocalizations { name: 'attachments'); } - String get show_all { + String get showAll { return Intl.message( 'Show all', - name: 'show_all'); + name: 'showAll'); } String get message_dialog_send_email_without_a_subject {