TF-296 Fix download file on browser
This commit is contained in:
@@ -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)),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,8 @@ dependencies:
|
||||
# collection
|
||||
collection: 1.15.0
|
||||
|
||||
universal_html: 2.0.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user