TF-296 Fix download file on browser

This commit is contained in:
dab246
2022-03-03 13:54:23 +07:00
committed by Dat H. Pham
parent a24a4a2bfd
commit 7beebfebc8
19 changed files with 221 additions and 32 deletions
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html;
import 'package:core/core.dart';
import 'package:dio/dio.dart';
@@ -72,4 +73,31 @@ class DownloadManager {
}
return streamController.stream.first;
}
Future<bool> downloadFileForWeb(String downloadUrl, String filename, String basicAuth) async {
final headerParam = Map<String, String>();
headerParam[HttpHeaders.authorizationHeader] = basicAuth;
headerParam[HttpHeaders.acceptHeader] = DioClient.jmapHeader;
http.Response res = await http.get(Uri.parse(downloadUrl), headers: headerParam);
if (res.statusCode == 200) {
final blob = html.Blob([res.bodyBytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.document.createElement('a') as html.AnchorElement
..href = url
..style.display = 'none'
..download = filename;
html.document.body?.children.add(anchor);
anchor.click();
html.document.body?.children.remove(anchor);
html.Url.revokeObjectUrl(url);
return true;
}
return false;
}
}
@@ -1,6 +1,7 @@
import 'package:core/core.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
typedef OnCancelDownloadActionClick = void Function();
@@ -57,13 +58,13 @@ class DownloadingFileDialogBuilder {
),
)),
actions: [
TextButton(
onPressed: () {
if (_onCancelDownloadActionClick != null) {
_onCancelDownloadActionClick!();
}},
child: Text(_actionText, style: TextStyle(fontSize: 17.0, color: AppColor.appColor)),
)
if (_actionText.isNotEmpty)
Padding(
padding: EdgeInsets.only(bottom: kIsWeb ? 16 : 0, top: kIsWeb ? 16 : 0),
child: TextButton(
onPressed: () => _onCancelDownloadActionClick?.call(),
child: Text(_actionText, style: TextStyle(fontSize: 17.0, color: AppColor.appColor)),
))
],
);
}
+2
View File
@@ -76,6 +76,8 @@ dependencies:
# collection
collection: 1.15.0
universal_html: 2.0.8
dev_dependencies:
flutter_test:
sdk: flutter
+1 -1
View File
@@ -1 +1 @@
SERVER_URL=https://dev.open-paas.org
SERVER_URL=http://localhost
@@ -5,7 +5,6 @@ import 'dart:io';
import 'package:core/core.dart';
import 'package:dio/dio.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/main/utils/app_logger.dart';
class ComposerAPI {
@@ -14,7 +13,6 @@ class ComposerAPI {
ComposerAPI(this._dioClient);
Future<UploadResponse> uploadAttachment(UploadRequest uploadRequest) async {
log('uploadAttachment: fileInfo: ${uploadRequest.fileInfo.props}');
final headerParam = _dioClient.getHeaders();
headerParam[HttpHeaders.contentTypeHeader] = uploadRequest.fileInfo.mimeType;
headerParam[HttpHeaders.contentLengthHeader] = uploadRequest.fileInfo.fileSize;
@@ -27,6 +27,13 @@ abstract class EmailDataSource {
CancelToken cancelToken
);
Future<bool> downloadAttachmentForWeb(
Attachment attachment,
AccountId accountId,
String baseDownloadUrl,
AccountRequest accountRequest,
);
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
Future<List<Email>> markAsStar(
@@ -113,4 +113,13 @@ class EmailDataSourceImpl extends EmailDataSource {
throw error;
});
}
@override
Future<bool> downloadAttachmentForWeb(Attachment attachment, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest) {
return Future.sync(() async {
return await emailAPI.downloadAttachmentForWeb(attachment, accountId, baseDownloadUrl, accountRequest);
}).catchError((error) {
throw error;
});
}
}
@@ -202,6 +202,19 @@ class EmailAPI {
cancelToken: cancelToken);
}
Future<bool> downloadAttachmentForWeb(
Attachment attachment,
AccountId accountId,
String baseDownloadUrl,
AccountRequest accountRequest,
) async {
return _downloadManager.downloadFileForWeb(
attachment.getDownloadUrl(baseDownloadUrl, accountId),
attachment.name ?? '',
accountRequest.basicAuth,
);
}
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest) async {
final setEmailMethod = SetEmailMethod(accountId)
..addUpdates(moveRequest.emailIds
@@ -103,4 +103,9 @@ class EmailRepositoryImpl extends EmailRepository {
Future<Email?> updateEmailDrafts(AccountId accountId, Email newEmail, EmailId oldEmailId) {
return emailDataSource.updateEmailDrafts(accountId, newEmail, oldEmailId);
}
@override
Future<bool> downloadAttachmentForWeb(Attachment attachment, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest) {
return emailDataSource.downloadAttachmentForWeb(attachment, accountId, baseDownloadUrl, accountRequest);
}
}
@@ -27,6 +27,13 @@ abstract class EmailRepository {
CancelToken cancelToken
);
Future<bool> downloadAttachmentForWeb(
Attachment attachment,
AccountId accountId,
String baseDownloadUrl,
AccountRequest accountRequest,
);
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
Future<List<Email>> markAsStar(
@@ -0,0 +1,18 @@
import 'package:core/core.dart';
class DownloadAttachmentForWebSuccess extends UIState {
DownloadAttachmentForWebSuccess();
@override
List<Object> get props => [];
}
class DownloadAttachmentForWebFailure extends FeatureFailure {
final exception;
DownloadAttachmentForWebFailure(this.exception);
@override
List<Object> get props => [exception];
}
@@ -0,0 +1,39 @@
import 'dart:async';
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
class DownloadAttachmentForWebInteractor {
final EmailRepository emailRepository;
final CredentialRepository credentialRepository;
DownloadAttachmentForWebInteractor(this.emailRepository, this.credentialRepository);
Stream<Either<Failure, Success>> execute(Attachment attachment, AccountId accountId, String baseDownloadUrl,) async* {
try {
final result = await Future.wait(
[credentialRepository.getUserName(), credentialRepository.getPassword()],
eagerError: true
).then((List responses) async {
final accountRequest = AccountRequest(responses.first, responses.last);
return await emailRepository.downloadAttachmentForWeb(
attachment,
accountId,
baseDownloadUrl,
accountRequest);
});
if (result) {
yield Right<Failure, Success>(DownloadAttachmentForWebSuccess());
} else {
yield Left<Failure, Success>(DownloadAttachmentForWebFailure(null));
}
} catch (exception) {
yield Left<Failure, Success>(DownloadAttachmentForWebFailure(exception));
}
}
}
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
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_attachment_for_web_interactor.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';
@@ -38,6 +39,7 @@ class EmailBindings extends BaseBindings {
Get.find<ExportAttachmentInteractor>(),
Get.find<MoveToMailboxInteractor>(),
Get.find<MarkAsStarEmailInteractor>(),
Get.find<DownloadAttachmentForWebInteractor>(),
));
}
@@ -70,6 +72,10 @@ class EmailBindings extends BaseBindings {
));
Get.lazyPut(() => MoveToMailboxInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MarkAsStarEmailInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => DownloadAttachmentForWebInteractor(
Get.find<EmailRepository>(),
Get.find<CredentialRepository>(),
));
}
@override
@@ -7,14 +7,17 @@ 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:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_request.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/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/state/move_to_mailbox_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.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';
@@ -43,6 +46,7 @@ class EmailController extends BaseController {
final ExportAttachmentInteractor _exportAttachmentInteractor;
final MoveToMailboxInteractor _moveToMailboxInteractor;
final MarkAsStarEmailInteractor _markAsStarEmailInteractor;
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
final emailAddressExpandMode = ExpandMode.COLLAPSE.obs;
final attachmentsExpandMode = ExpandMode.COLLAPSE.obs;
@@ -60,6 +64,7 @@ class EmailController extends BaseController {
this._exportAttachmentInteractor,
this._moveToMailboxInteractor,
this._markAsStarEmailInteractor,
this._downloadAttachmentForWebInteractor,
);
@override
@@ -107,6 +112,8 @@ class EmailController extends BaseController {
_downloadAttachmentsFailure(failure);
} else if (failure is ExportAttachmentFailure) {
_exportAttachmentFailureAction(failure);
} else if (failure is DownloadAttachmentForWebFailure) {
_downloadAttachmentForWebFailureAction(failure);
}
},
(success) {
@@ -120,6 +127,8 @@ class EmailController extends BaseController {
_moveToMailboxSuccess(success);
} else if (success is MarkAsStarEmailSuccess) {
_markAsEmailStarSuccess(success);
} else if (success is DownloadAttachmentForWebSuccess) {
_downloadAttachmentForWebSuccessAction(success);
}
});
}
@@ -229,23 +238,35 @@ class EmailController extends BaseController {
void exportAttachment(BuildContext context, Attachment attachment) {
final cancelToken = CancelToken();
_showDownloadingFileDialog(context, attachment, cancelToken);
_showDownloadingFileDialog(context, attachment, cancelToken: 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]);
popBack();
}))
.build());
void _showDownloadingFileDialog(BuildContext context, Attachment attachment, {CancelToken? cancelToken}) {
if (cancelToken != null) {
showCupertinoDialog(
context: context,
builder: (_) =>
PointerInterceptor(child: (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]);
popBack();
}))
.build()));
} else {
showCupertinoDialog(
context: context,
builder: (_) =>
PointerInterceptor(child: (DownloadingFileDialogBuilder()
..key(Key('downloading_file_for_web_dialog'))
..title(AppLocalizations.of(context).preparing_to_save)
..content(AppLocalizations.of(context).downloading_file(attachment.name ?? '')))
.build()));
}
}
void _exportAttachmentAction(Attachment attachment, CancelToken cancelToken) async {
@@ -269,6 +290,28 @@ class EmailController extends BaseController {
}
}
void downloadAttachmentForWeb(Attachment attachment) {
_downloadAttachmentForWebAction(attachment);
}
void _downloadAttachmentForWebAction(Attachment attachment) async {
final accountId = mailboxDashBoardController.accountId.value;
if (accountId != null && mailboxDashBoardController.sessionCurrent != null) {
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent!.getDownloadUrl();
consumeState(_downloadAttachmentForWebInteractor.execute(attachment, accountId, baseDownloadUrl));
}
}
void _downloadAttachmentForWebFailureAction(Failure failure) {
if (failure is DownloadAttachmentForWebFailure) {
popBack();
}
}
void _downloadAttachmentForWebSuccessAction(Success success) async {
popBack();
}
void openDestinationPickerView(PresentationEmail email) async {
final currentMailbox = mailboxDashBoardController.selectedMailbox.value;
final accountId = mailboxDashBoardController.accountId.value;
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:core/core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
@@ -221,10 +222,14 @@ class EmailView extends GetView {
..setExpandMode((countAttachments - 1 == index) ? emailController.attachmentsExpandMode.value : null)
..onExpandAttachmentActionClick(() => emailController.toggleDisplayAttachmentsAction())
..onDownloadAttachmentFileActionClick((attachment) {
if (Platform.isAndroid) {
emailController.downloadAttachments(context, [attachment]);
if (kIsWeb) {
emailController.downloadAttachmentForWeb(attachment);
} else {
emailController.exportAttachment(context, attachment);
if (Platform.isAndroid) {
emailController.downloadAttachments(context, [attachment]);
} else if (Platform.isIOS) {
emailController.exportAttachment(context, attachment);
}
}
}))
.build())
@@ -646,7 +646,7 @@ class AppLocalizations {
String get create_new_mailbox_failure {
return Intl.message(
'Create new mailbox failure',
name: 'new_mailbox_is_created'
name: 'create_new_mailbox_failure'
);
}
@@ -729,4 +729,11 @@ class AppLocalizations {
name: 'email_address_is_not_in_the_correct_format',
);
}
String get preparing_to_save {
return Intl.message(
'Preparing to save',
name: 'preparing_to_save'
);
}
}
-3
View File
@@ -130,9 +130,6 @@ dependencies:
# fk_user_agent
fk_user_agent: 2.1.0
# pointer_interceptor
pointer_interceptor: 0.9.3
dev_dependencies:
flutter_test:
sdk: flutter
@@ -1,3 +1,5 @@
@TestOn('vm')
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
@@ -1,3 +1,5 @@
@TestOn('vm')
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';