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]
);
}
}