From d168900975e23eab4868ced5929856005fa15f6d Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 16 Sep 2021 09:46:49 +0700 Subject: [PATCH] TF-32 Add presentation layer of export attachment for ios platform --- ios/Runner/AppDelegate.swift | 6 + ios/Runner/Info.plist | 2 + .../email/presentation/email_bindings.dart | 16 +- .../email/presentation/email_controller.dart | 103 ++++++++-- .../email/presentation/email_view.dart | 191 +++++++++++++----- .../extensions/list_attachment_extension.dart | 14 ++ .../widgets/attachment_file_tile_builder.dart | 105 +++++++--- .../home/presentation/home_controller.dart | 5 +- .../login/presentation/login_controller.dart | 3 +- .../presentation/mailbox_controller.dart | 3 +- .../presentation/session_controller.dart | 5 +- .../presentation/thread_controller.dart | 2 +- lib/l10n/intl_en.arb | 26 ++- lib/l10n/intl_fr.arb | 26 ++- lib/l10n/intl_messages.arb | 28 ++- lib/l10n/intl_ru.arb | 26 ++- lib/l10n/intl_vi.arb | 26 ++- .../bindings/network/network_bindings.dart | 6 +- lib/main/localizations/app_localizations.dart | 43 +++- 19 files changed, 517 insertions(+), 119 deletions(-) create mode 100644 lib/features/email/presentation/extensions/list_attachment_extension.dart diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4a8..6754bd46c 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import Flutter +import flutter_downloader @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -8,6 +9,11 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + FlutterDownloaderPlugin.setPluginRegistrantCallback { registry in + if (!registry.hasPlugin("FlutterDownloaderPlugin")) { + FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!) + } + } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 762f52f0d..983b0829c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -26,6 +26,8 @@ LaunchScreen UIMainStoryboardFile Main + NSPhotoLibraryAddUsageDescription + To save photo to library, we need permission to access your photo library. UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/features/email/presentation/email_bindings.dart b/lib/features/email/presentation/email_bindings.dart index 6b156840c..91b464f84 100644 --- a/lib/features/email/presentation/email_bindings.dart +++ b/lib/features/email/presentation/email_bindings.dart @@ -7,6 +7,7 @@ import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/download_attachments_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/email_controller.dart'; @@ -22,17 +23,20 @@ class EmailBindings extends Bindings { Get.lazyPut(() => Get.find()); Get.lazyPut(() => GetEmailContentInteractor(Get.find())); Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find())); - Get.put(EmailController( - Get.find(), - Get.find())); Get.lazyPut(() => CredentialRepositoryImpl(Get.find())); Get.lazyPut(() => Get.find()); Get.lazyPut(() => DownloadAttachmentsInteractor( Get.find(), Get.find())); + Get.lazyPut(() => ExportAttachmentInteractor( + Get.find(), + Get.find())); Get.put(EmailController( - Get.find(), - Get.find(), - Get.find())); + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find())); } } \ No newline at end of file diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index d4bf7a3d0..3afdcb095 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -1,16 +1,21 @@ -import 'dart:io'; - import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/email/domain/state/download_attachments_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/export_attachment_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/download_attachments_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; @@ -18,6 +23,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_da import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; +import 'package:share/share.dart' as share_library; class EmailController extends BaseController { @@ -28,22 +34,27 @@ class EmailController extends BaseController { final MarkAsEmailReadInteractor _markAsEmailReadInteractor; final DownloadAttachmentsInteractor _downloadAttachmentsInteractor; final DeviceManager _deviceManager; + final AppToast _appToast; + final ExportAttachmentInteractor _exportAttachmentInteractor; final emailAddressExpandMode = ExpandMode.COLLAPSE.obs; - EmailContent? emailContent; + final attachmentsExpandMode = ExpandMode.COLLAPSE.obs; + final emailContent = Rxn(); - EmailController(this._getEmailContentInteractor, this._markAsEmailReadInteractor); EmailController( this._getEmailContentInteractor, + this._markAsEmailReadInteractor, this._downloadAttachmentsInteractor, - this._deviceManager + this._deviceManager, + this._appToast, + this._exportAttachmentInteractor, ); @override void onReady() { super.onReady(); mailboxDashBoardController.selectedEmail.listen((presentationEmail) { - toggleDisplayEmailAddressAction(expandMode: ExpandMode.COLLAPSE); + _clearEmailContent(); final accountId = mailboxDashBoardController.accountId.value; if (accountId != null && presentationEmail != null) { _getEmailContentAction(accountId, presentationEmail.id); @@ -75,13 +86,19 @@ class EmailController extends BaseController { (failure) { if (failure is MarkAsEmailReadFailure) { _markAsEmailReadFailure(failure); + } else if (failure is DownloadAttachmentsFailure) { + _downloadAttachmentsFailure(failure); + } else if (failure is ExportAttachmentFailure) { + _exportAttachmentFailureAction(failure); } }, (success) { if (success is GetEmailContentSuccess) { - emailContent = success.emailContent; + emailContent.value = success.emailContent; } else if (success is MarkAsEmailReadSuccess) { _markAsEmailReadSuccess(success); + } else if (success is ExportAttachmentSuccess) { + _exportAttachmentSuccessAction(success); } }); } @@ -90,6 +107,12 @@ class EmailController extends BaseController { void onError(error) { } + void _clearEmailContent() { + toggleDisplayEmailAddressAction(expandMode: ExpandMode.COLLAPSE); + attachmentsExpandMode.value = ExpandMode.COLLAPSE; + emailContent.value = null; + } + void toggleDisplayEmailAddressAction({required ExpandMode expandMode}) { emailAddressExpandMode.value = expandMode; } @@ -114,10 +137,17 @@ class EmailController extends BaseController { backToThreadView(); } + void toggleDisplayAttachmentsAction() { + final newExpandMode = attachmentsExpandMode.value == ExpandMode.COLLAPSE + ? ExpandMode.EXPAND + : ExpandMode.COLLAPSE; + attachmentsExpandMode.value = newExpandMode; + } + void downloadAttachments(BuildContext context, List attachments) async { final needRequestPermission = await _deviceManager.isNeedRequestStoragePermissionOnAndroid(); - if (Platform.isAndroid && needRequestPermission) { + if (needRequestPermission) { final status = await Permission.storage.status; switch (status) { case PermissionStatus.granted: @@ -147,10 +177,55 @@ class EmailController extends BaseController { final accountId = mailboxDashBoardController.accountId.value; if (accountId != null && mailboxDashBoardController.sessionCurrent != null) { final baseDownloadUrl = mailboxDashBoardController.sessionCurrent!.getDownloadUrl(); - await _downloadAttachmentsInteractor.execute(attachments, accountId, baseDownloadUrl) - .then((result) => result.fold( - (failure) => AppLocalizations.of(context).attachment_download_failed, - (success) => AppLocalizations.of(context).attachment_download_successfully)); + consumeState(_downloadAttachmentsInteractor.execute(attachments, accountId, baseDownloadUrl)); + } + } + + void _downloadAttachmentsFailure(Failure failure) { + if (Get.context != null) { + _appToast.showErrorToast(AppLocalizations.of(Get.context!).attachment_download_failed); + } + } + + void exportAttachment(BuildContext context, Attachment attachment) { + final cancelToken = CancelToken(); + _showDownloadingFileDialog(context, attachment, cancelToken); + _exportAttachmentAction(attachment, cancelToken); + } + + void _showDownloadingFileDialog(BuildContext context, Attachment attachment, CancelToken cancelToken) { + showCupertinoDialog( + context: context, + builder: (_) => (DownloadingFileDialogBuilder() + ..key(Key('downloading_file_dialog')) + ..title(AppLocalizations.of(context).preparing_to_export) + ..content(AppLocalizations.of(context).downloading_file(attachment.name ?? '')) + ..actionText(AppLocalizations.of(context).cancel) + ..addCancelDownloadActionClick(() { + cancelToken.cancel([AppLocalizations.of(context).user_cancel_download_file]); + Get.back(); + })) + .build()); + } + + void _exportAttachmentAction(Attachment attachment, CancelToken cancelToken) async { + final accountId = mailboxDashBoardController.accountId.value; + if (accountId != null && mailboxDashBoardController.sessionCurrent != null) { + final baseDownloadUrl = mailboxDashBoardController.sessionCurrent!.getDownloadUrl(); + consumeState(_exportAttachmentInteractor.execute(attachment, accountId, baseDownloadUrl, cancelToken)); + } + } + + void _exportAttachmentFailureAction(Failure failure) { + if (failure is ExportAttachmentFailure && !(failure.exception is CancelDownloadFileException)) { + popBack(); + } + } + + void _exportAttachmentSuccessAction(Success success) async { + popBack(); + if (success is ExportAttachmentSuccess) { + await share_library.Share.shareFiles([success.filePath]); } } @@ -165,12 +240,12 @@ class EmailController extends BaseController { void pressEmailAction(EmailActionType emailActionType) { if (canComposeEmail()) { - Get.toNamed( + push( AppRoutes.COMPOSER, arguments: ComposerArguments( emailActionType: emailActionType, presentationEmail: mailboxDashBoardController.selectedEmail.value!, - emailContent: emailContent, + emailContent: emailContent.value, session: mailboxDashBoardController.sessionCurrent!, userProfile: mailboxDashBoardController.userProfile.value!, mapMailboxId: mailboxDashBoardController.mapMailboxId)); diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 63b87603e..5bbec4d2b 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -1,17 +1,21 @@ +import 'dart:io'; + import 'package:core/core.dart'; import 'package:flutter/cupertino.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/domain/state/get_email_content_state.dart'; import 'package:tmail_ui_user/features/email/presentation/email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/app_bar_mail_widget_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/email_content_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/list_attachment_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/message_content_tile_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/sender_and_receiver_information_tile_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:filesize/filesize.dart'; class EmailView extends GetWidget { @@ -138,11 +142,11 @@ class EmailView extends GetWidget { imagePaths, emailController.mailboxDashBoardController.selectedEmail.value, emailController.emailAddressExpandMode.value) - .onOpenExpandAddressReceiverActionClick(() => emailController.toggleDisplayEmailAddressAction(expandMode: ExpandMode.EXPAND)) + .onOpenExpandAddressReceiverActionClick(() => emailController.toggleDisplayAttachmentsAction()) .build()), _buildLoadingView(), - _buildListMessageContent(), _buildListAttachments(context), + _buildListMessageContent(), ], ) ); @@ -150,66 +154,151 @@ class EmailView extends GetWidget { Widget _buildListAttachments(BuildContext context) { if (emailController.mailboxDashBoardController.selectedEmail.value?.hasAttachment == true) { - return Obx(() => emailController.viewState.value.fold( - (failure) => SizedBox.shrink(), - (success) { - if (success is GetEmailContentSuccess && success.emailContent != null) { - final attachments = success.emailContent!.getListAttachment(); - return attachments.isNotEmpty - ? GridView.builder( - key: Key('list_attachment'), - primary: false, - shrinkWrap: true, - padding: EdgeInsets.only(top: 16), - itemCount: attachments.length, - gridDelegate: SliverGridDelegateFixedHeight( - height: 60, - crossAxisCount: responsiveUtils.isMobile(context) ? 2 : 4, - crossAxisSpacing: 16.0, - mainAxisSpacing: 8.0), - itemBuilder: (context, index) => - (AttachmentFileTileBuilder(imagePaths, attachments[index]) - ..onDownloadAttachmentFileActionClick((attachment) => - emailController.downloadAttachments(context, [attachment]))) - .build()) + return Obx(() { + if (emailController.emailContent.value != null) { + final attachments = emailController.emailContent.value!.getListAttachment(); + return attachments.isNotEmpty + ? _buildAttachmentsBody(context, attachments) : SizedBox.shrink(); - } else { - return SizedBox.shrink(); - } - }) - ); + } else { + return SizedBox.shrink(); + } + }); } else { return SizedBox.shrink(); } } + int _getAttachmentLimitDisplayed(BuildContext context) { + if (responsiveUtils.isMobile(context)) { + return 2; + } else if (responsiveUtils.isTablet(context)) { + return 4; + } else { + return 3; + } + } + + Widget _buildAttachmentsBody(BuildContext context, List attachments) { + final attachmentLimitDisplayed = _getAttachmentLimitDisplayed(context); + final countAttachments = _getListAttachmentsSize( + context, + emailController.attachmentsExpandMode.value, + attachments, + attachmentLimitDisplayed); + final isExpand = emailController.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: Key('list_attachment'), + primary: false, + shrinkWrap: true, + padding: EdgeInsets.only(top: isExpand ? 0 : 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) ? emailController.attachmentsExpandMode.value : null) + ..onExpandAttachmentActionClick(() => emailController.toggleDisplayAttachmentsAction()) + ..onDownloadAttachmentFileActionClick((attachment) { + if (Platform.isAndroid) { + emailController.downloadAttachments(context, [attachment]); + } else { + emailController.exportAttachment(context, attachment); + } + })) + .build()) + ], + ); + } + + 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: TextStyle(fontSize: 12, color: AppColor.baseTextColor)), + Padding( + padding: EdgeInsets.only(left: 8), + child: Text( + '(${filesize(attachments.totalSize(), 1)})', + style: TextStyle(fontSize: 12, color: AppColor.nameUserColor, fontWeight: FontWeight.w500))) + ], + ), + if (attachments.length > 2) + Padding( + padding: EdgeInsets.only(left: 8), + child: IconButton( + icon: SvgPicture.asset(imagePaths.icExpandAttachment, + width: 20, + height: 20, + fit: BoxFit.fill), + onPressed: () => emailController.toggleDisplayAttachmentsAction() + )) + ], + ); + } + Widget _buildListMessageContent() { - return Obx(() => emailController.viewState.value.fold( - (failure) => SizedBox.shrink(), - (success) { - if (success is GetEmailContentSuccess && success.emailContent != null) { - final messageContents = success.emailContent!.getListMessageContent(); - final attachmentsInline = success.emailContent!.getListAttachmentInline(); - return messageContents.isNotEmpty - ? ListView.builder( - primary: false, - shrinkWrap: true, - padding: EdgeInsets.only(top: 16), - key: Key('list_message_content'), - itemCount: messageContents.length, - itemBuilder: (context, index) => + return Obx(() { + if (emailController.emailContent.value != null) { + final messageContents = emailController.emailContent.value!.getListMessageContent(); + final attachmentsInline = emailController.emailContent.value!.getListAttachmentInline(); + return messageContents.isNotEmpty + ? ListView.builder( + primary: false, + shrinkWrap: true, + padding: EdgeInsets.only(top: 16), + key: Key('list_message_content'), + itemCount: messageContents.length, + itemBuilder: (context, index) => MessageContentTileBuilder( htmlMessagePurifier: htmlMessagePurifier, messageContent: messageContents[index], attachmentInlines: attachmentsInline, session: emailController.mailboxDashBoardController.sessionCurrent, accountId: emailController.mailboxDashBoardController.accountId.value) - .build()) - : SizedBox.shrink(); - } else { - return SizedBox.shrink(); - } - }) - ); + .build()) + : SizedBox.shrink(); + } else { + return SizedBox.shrink(); + } + }); } } \ No newline at end of file diff --git a/lib/features/email/presentation/extensions/list_attachment_extension.dart b/lib/features/email/presentation/extensions/list_attachment_extension.dart new file mode 100644 index 000000000..8bc3f1543 --- /dev/null +++ b/lib/features/email/presentation/extensions/list_attachment_extension.dart @@ -0,0 +1,14 @@ +import 'package:model/model.dart'; + +extension ListAttachmentExtension on List { + + num totalSize() { + num totalSize = 0; + forEach((attachment) { + if (attachment.size != null) { + totalSize += attachment.size!.value; + } + }); + return totalSize; + } +} \ No newline at end of file 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 934c55e83..a9060bb80 100644 --- a/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart +++ b/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart @@ -7,20 +7,38 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:model/model.dart'; typedef OnDownloadAttachmentFileActionClick = void Function(Attachment attachment); +typedef OnExpandAttachmentActionClick = void Function(); class AttachmentFileTileBuilder { final ImagePaths _imagePaths; final Attachment _attachment; + final int _attachmentSize; + final int _limitDisplayAttachment; + ExpandMode? _expandMode; OnDownloadAttachmentFileActionClick? _onDownloadAttachmentFileActionClick; + OnExpandAttachmentActionClick? _onExpandAttachmentActionClick; - AttachmentFileTileBuilder(this._imagePaths, this._attachment); + 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; + } + Widget build() { return Theme( data: ThemeData( @@ -35,37 +53,72 @@ class AttachmentFileTileBuilder { color: Colors.white), child: MediaQuery( data: MediaQueryData(padding: EdgeInsets.zero), - child: ListTile( - contentPadding: EdgeInsets.zero, - focusColor: AppColor.primaryColor, - hoverColor: AppColor.primaryColor, - onTap: () { - if (_onDownloadAttachmentFileActionClick != null) { - _onDownloadAttachmentFileActionClick!(_attachment); - }}, - leading: Transform( - transform: Matrix4.translationValues(14.0, 2.0, 0.0), - child: SvgPicture.asset(_imagePaths.icAttachmentFile, width: 24, height: 24, fit: BoxFit.fill)), - title: Transform( - transform: Matrix4.translationValues(-8.0, -8.0, 0.0), - child: Text( - _attachment.name ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 12, color: AppColor.attachmentFileNameColor, fontWeight: FontWeight.w500), - )), - subtitle: _attachment.size != null && _attachment.size?.value != 0 - ? Transform( + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + ListTile( + contentPadding: EdgeInsets.zero, + focusColor: AppColor.primaryColor, + hoverColor: AppColor.primaryColor, + onTap: () { + if (_onDownloadAttachmentFileActionClick != null) { + _onDownloadAttachmentFileActionClick!(_attachment); + }}, + leading: Transform( + transform: Matrix4.translationValues(14.0, 2.0, 0.0), + child: SvgPicture.asset(_imagePaths.icAttachmentFile, width: 24, height: 24, fit: BoxFit.fill)), + title: Transform( transform: Matrix4.translationValues(-8.0, -8.0, 0.0), child: Text( - filesize(_attachment.size?.value), + _attachment.name ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 12, color: AppColor.attachmentFileSizeColor))) - : null - ), + style: TextStyle(fontSize: 12, color: AppColor.attachmentFileNameColor, fontWeight: FontWeight.w500), + )), + subtitle: _attachment.size != null && _attachment.size?.value != 0 + ? Transform( + transform: Matrix4.translationValues(-8.0, -8.0, 0.0), + child: Text( + filesize(_attachment.size?.value), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 12, color: AppColor.attachmentFileSizeColor))) + : null + ), + Transform( + transform: Matrix4.translationValues(5.0, 5.0, 0.0), + child: IconButton( + icon: SvgPicture.asset(_imagePaths.icDownload, width: 24, height: 24, 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: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold)) + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart index d1efae48f..0e18d71e1 100644 --- a/lib/features/home/presentation/home_controller.dart +++ b/lib/features/home/presentation/home_controller.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_credential_state.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_credential_interactor.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class HomeController extends GetxController { final GetCredentialInteractor _getCredentialInteractor; @@ -36,7 +37,7 @@ class HomeController extends GetxController { } void _goToLogin() { - Get.offNamed(AppRoutes.LOGIN); + pushAndPop(AppRoutes.LOGIN); } void _goToMailbox(GetCredentialViewState credentialViewState) { @@ -45,6 +46,6 @@ class HomeController extends GetxController { credentialViewState.userName.userName, credentialViewState.password.value, ); - Get.offNamed(AppRoutes.SESSION); + pushAndPop(AppRoutes.SESSION); } } \ No newline at end of file diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index e116792a8..650ec6f24 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -6,6 +6,7 @@ import 'package:tmail_ui_user/features/login/domain/state/authentication_user_st import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/state/login_state.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class LoginController extends GetxController { @@ -60,7 +61,7 @@ class LoginController extends GetxController { loginState.value = LoginState(Right(success)); _dynamicUrlInterceptors.changeBaseUrl(_urlText); _authorizationInterceptors.changeAuthorization(_userNameText, _passwordText); - Get.offNamed(AppRoutes.SESSION); + pushAndPop(AppRoutes.SESSION); } void _loginFailureAction(AuthenticationUserFailure failure) { diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index e46f3912a..991c169fe 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_b import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class MailboxController extends BaseController { @@ -141,6 +142,6 @@ class MailboxController extends BaseController { void closeMailboxScreen() { _deleteCredential(); - Get.offAllNamed(AppRoutes.LOGIN); + pushAndPopAll(AppRoutes.LOGIN); } } \ No newline at end of file diff --git a/lib/features/session/presentation/session_controller.dart b/lib/features/session/presentation/session_controller.dart index 87efb250e..897cdd8b1 100644 --- a/lib/features/session/presentation/session_controller.dart +++ b/lib/features/session/presentation/session_controller.dart @@ -3,6 +3,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_i import 'package:tmail_ui_user/features/session/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/session/domain/usecases/get_session_interactor.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class SessionController extends GetxController { final GetSessionInteractor _getSessionInteractor; @@ -29,10 +30,10 @@ class SessionController extends GetxController { void _goToLogin() { _deleteCredential(); - Get.offNamed(AppRoutes.LOGIN); + pushAndPop(AppRoutes.LOGIN); } void _goToMailboxDashBoard(GetSessionSuccess getSessionSuccess) { - Get.offNamed(AppRoutes.MAILBOX_DASHBOARD, arguments: getSessionSuccess.session); + pushAndPop(AppRoutes.MAILBOX_DASHBOARD, arguments: getSessionSuccess.session); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 93aa9957d..18e7e303f 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -392,7 +392,7 @@ class ThreadController extends BaseController { void composeEmailAction() { if (canComposeEmail()) { - Get.toNamed( + push( AppRoutes.COMPOSER, arguments: ComposerArguments( session: mailboxDashBoardController.sessionCurrent!, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 33aef1d28..fb9b7aaf7 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -276,8 +276,30 @@ "placeholders_order": [], "placeholders": {} }, - "attachment_download_successfully": "Attachment downloaded successfully", - "@attachment_download_successfully": { + "downloading_file": "Downloading {fileName}", + "@downloading_file": { + "type": "text", + "placeholders_order": [ + "fileName" + ], + "placeholders": { + "fileName": {} + } + }, + "preparing_to_export": "Preparing to export", + "@preparing_to_export": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "cancel": "Cancel", + "@cancel": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "user_cancel_download_file": "User cancel download file", + "@user_cancel_download_file": { "type": "text", "placeholders_order": [], "placeholders": {} diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 33aef1d28..fb9b7aaf7 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -276,8 +276,30 @@ "placeholders_order": [], "placeholders": {} }, - "attachment_download_successfully": "Attachment downloaded successfully", - "@attachment_download_successfully": { + "downloading_file": "Downloading {fileName}", + "@downloading_file": { + "type": "text", + "placeholders_order": [ + "fileName" + ], + "placeholders": { + "fileName": {} + } + }, + "preparing_to_export": "Preparing to export", + "@preparing_to_export": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "cancel": "Cancel", + "@cancel": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "user_cancel_download_file": "User cancel download file", + "@user_cancel_download_file": { "type": "text", "placeholders_order": [], "placeholders": {} diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index b8a6f634a..72a08f66a 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2021-09-15T23:44:29.035668", + "@@last_modified": "2021-09-16T14:31:23.463339", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -276,8 +276,30 @@ "placeholders_order": [], "placeholders": {} }, - "attachment_download_successfully": "Attachment downloaded successfully", - "@attachment_download_successfully": { + "downloading_file": "Downloading {fileName}", + "@downloading_file": { + "type": "text", + "placeholders_order": [ + "fileName" + ], + "placeholders": { + "fileName": {} + } + }, + "preparing_to_export": "Preparing to export", + "@preparing_to_export": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "cancel": "Cancel", + "@cancel": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "user_cancel_download_file": "User cancel download file", + "@user_cancel_download_file": { "type": "text", "placeholders_order": [], "placeholders": {} diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 33aef1d28..fb9b7aaf7 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -276,8 +276,30 @@ "placeholders_order": [], "placeholders": {} }, - "attachment_download_successfully": "Attachment downloaded successfully", - "@attachment_download_successfully": { + "downloading_file": "Downloading {fileName}", + "@downloading_file": { + "type": "text", + "placeholders_order": [ + "fileName" + ], + "placeholders": { + "fileName": {} + } + }, + "preparing_to_export": "Preparing to export", + "@preparing_to_export": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "cancel": "Cancel", + "@cancel": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "user_cancel_download_file": "User cancel download file", + "@user_cancel_download_file": { "type": "text", "placeholders_order": [], "placeholders": {} diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 33aef1d28..fb9b7aaf7 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -276,8 +276,30 @@ "placeholders_order": [], "placeholders": {} }, - "attachment_download_successfully": "Attachment downloaded successfully", - "@attachment_download_successfully": { + "downloading_file": "Downloading {fileName}", + "@downloading_file": { + "type": "text", + "placeholders_order": [ + "fileName" + ], + "placeholders": { + "fileName": {} + } + }, + "preparing_to_export": "Preparing to export", + "@preparing_to_export": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "cancel": "Cancel", + "@cancel": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "user_cancel_download_file": "User cancel download file", + "@user_cancel_download_file": { "type": "text", "placeholders_order": [], "placeholders": {} diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index c3ccfe5ad..6309a4130 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -45,9 +45,13 @@ class NetworkBindings extends Bindings { void _bindingApi() { Get.put(DioClient(Get.find())); Get.put(JmapHttpClient.HttpClient(Get.find())); + Get.put(DownloadClient(Get.find())); + Get.put(DownloadManager(Get.find())); Get.put(MailboxAPI(Get.find())); Get.put(SessionAPI(Get.find())); Get.put(ThreadAPI(Get.find())); - Get.put(EmailAPI(Get.find())); + Get.put(EmailAPI( + Get.find(), + Get.find())); } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 2aab4d733..74f23fed4 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -308,10 +308,47 @@ class AppLocalizations { ); } - String get attachment_download_successfully { + String downloading_file(String fileName) { return Intl.message( - 'Attachment downloaded successfully', - name: 'attachment_download_successfully', + 'Downloading $fileName', + name: 'downloading_file', + args: [fileName] + ); + } + + String get preparing_to_export { + return Intl.message( + 'Preparing to export', + name: 'preparing_to_export' + ); + } + + String get cancel { + return Intl.message( + 'Cancel', + name: 'cancel' + ); + } + + String get user_cancel_download_file { + return Intl.message( + 'User cancel download file', + name: 'user_cancel_download_file' + ); + } + + String get you_need_to_grant_files_permission_to_download_attachments { + return Intl.message( + 'You need to grant files permission to download attachments', + name: 'you_need_to_grant_files_permission_to_download_attachments' + ); + } + + String count_attachment(int count) { + return Intl.message( + '$count attachments', + name: 'count_attachment', + args: [count] ); } }