TF-32 Add domain layer of export attachment for ios platform
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 9L7.5 6L4.5 3" stroke="#3840F7" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 217 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 20V18H5V20H19ZM19 10H15V4H9V10H5L12 17L19 10Z" fill="#7B7B7B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 180 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 6L8 10L12 6" stroke="#3840F7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 211 B |
@@ -33,6 +33,7 @@ export 'presentation/views/text/text_form_field_builder.dart';
|
||||
export 'presentation/views/image/icon_builder.dart';
|
||||
export 'presentation/views/context_menu/context_menu_action_builder.dart';
|
||||
export 'presentation/views/context_menu/context_menu_builder.dart';
|
||||
export 'presentation/views/dialog/downloading_file_dialog_builder.dart';
|
||||
|
||||
// Resources
|
||||
export 'presentation/resources/assets_paths.dart';
|
||||
@@ -48,6 +49,8 @@ export 'data/network/config/dynamic_url_interceptors.dart';
|
||||
export 'data/network/config/endpoint.dart';
|
||||
export 'data/network/config/service_path.dart';
|
||||
export 'data/network/dio_client.dart';
|
||||
export 'data/network/download/download_client.dart';
|
||||
export 'data/network/download/download_manager.dart';
|
||||
|
||||
// State
|
||||
export 'presentation/state/success.dart';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class DownloadClient {
|
||||
|
||||
final DioClient _dioClient;
|
||||
|
||||
DownloadClient(this._dioClient);
|
||||
|
||||
Future<ResponseBody> downloadFile(
|
||||
String url,
|
||||
String baseAuth,
|
||||
CancelToken? cancelToken,
|
||||
) async {
|
||||
final headerParam = _dioClient.getHeaders();
|
||||
headerParam[HttpHeaders.authorizationHeader] = baseAuth;
|
||||
headerParam[HttpHeaders.acceptHeader] = DioClient.jmapHeader;
|
||||
|
||||
final responseBody = await _dioClient.get(
|
||||
url,
|
||||
options: Options(headers: headerParam, responseType: ResponseType.stream),
|
||||
cancelToken: cancelToken);
|
||||
|
||||
return (responseBody as ResponseBody);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/data/network/download/download_client.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class DownloadManager {
|
||||
final DownloadClient _downloadClient;
|
||||
|
||||
DownloadManager(this._downloadClient);
|
||||
|
||||
Future<String> downloadFile(
|
||||
String downloadUrl,
|
||||
Future<Directory> directoryToSave,
|
||||
String filename,
|
||||
String baseAuth,
|
||||
{CancelToken? cancelToken}
|
||||
) async {
|
||||
final streamController = StreamController<String>();
|
||||
|
||||
try {
|
||||
await Future.wait([
|
||||
_downloadClient.downloadFile(downloadUrl, baseAuth, cancelToken),
|
||||
directoryToSave
|
||||
]).then((values) {
|
||||
final fileStream = (values[0] as ResponseBody).stream;
|
||||
final tempFilePath = '${(values[1] as Directory).absolute.path}/$filename';
|
||||
|
||||
final file = File(tempFilePath);
|
||||
file.createSync(recursive: true);
|
||||
var randomAccessFile = file.openSync(mode: FileMode.write);
|
||||
late StreamSubscription subscription;
|
||||
|
||||
subscription = fileStream
|
||||
.takeWhile((_) => cancelToken == null || !cancelToken.isCancelled)
|
||||
.listen((data) {
|
||||
subscription.pause();
|
||||
randomAccessFile.writeFrom(data).then((_randomAccessFile) {
|
||||
randomAccessFile = _randomAccessFile;
|
||||
subscription.resume();
|
||||
if (cancelToken != null && cancelToken.isCancelled) {
|
||||
streamController.sink.addError(CancelDownloadFileException(cancelToken.cancelError?.message));
|
||||
}
|
||||
}).catchError((error) async {
|
||||
await subscription.cancel();
|
||||
streamController.sink.addError(CommonDownloadFileException(error.toString()));
|
||||
await streamController.close();
|
||||
});
|
||||
}, onDone: () async {
|
||||
await randomAccessFile.close();
|
||||
if (cancelToken != null && cancelToken.isCancelled) {
|
||||
streamController.sink.addError(CancelDownloadFileException(cancelToken.cancelError?.message));
|
||||
} else {
|
||||
streamController.sink.add(tempFilePath);
|
||||
}
|
||||
await streamController.close();
|
||||
}, onError: (error) async {
|
||||
await randomAccessFile.close();
|
||||
await file.delete();
|
||||
streamController.sink.addError(CommonDownloadFileException(error.toString()));
|
||||
await streamController.close();
|
||||
});
|
||||
});
|
||||
} catch(exception) {
|
||||
if (cancelToken != null && cancelToken.isCancelled) {
|
||||
streamController.sink.addError(CancelDownloadFileException(cancelToken.cancelError?.message));
|
||||
await streamController.close();
|
||||
return streamController.stream.first;
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
return streamController.stream.first;
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,5 @@ extension AppColor on Color {
|
||||
static const toastBackgroundColor = Color(0xFFACAFFF);
|
||||
static const toastSuccessBackgroundColor = Color(0xFF04AA11);
|
||||
static const toastErrorBackgroundColor = Color(0xFFFF5858);
|
||||
static const backgroundCountAttachment = Color(0x681C1C1C);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ class ImagePaths {
|
||||
String get icComposerFileShare => _getImagePath('ic_composer_file_share.svg');
|
||||
String get icExpandAddress => _getImagePath('ic_expand_address.svg');
|
||||
String get icSelected => _getImagePath('ic_selected.svg');
|
||||
String get icExpandAttachment => _getImagePath('ic_expand_attachment.svg');
|
||||
String get icDownload => _getImagePath('ic_download.svg');
|
||||
|
||||
String _getImagePath(String imageName) {
|
||||
return AssetsPaths.images + imageName;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
typedef OnCancelDownloadActionClick = void Function();
|
||||
|
||||
class DownloadingFileDialogBuilder {
|
||||
Key? _key;
|
||||
String _title = '';
|
||||
String _content = '';
|
||||
String _actionText = '';
|
||||
OnCancelDownloadActionClick? _onCancelDownloadActionClick;
|
||||
|
||||
DownloadingFileDialogBuilder();
|
||||
|
||||
void key(Key key) {
|
||||
_key = key;
|
||||
}
|
||||
|
||||
void title(String title) {
|
||||
_title = title;
|
||||
}
|
||||
|
||||
void content(String content) {
|
||||
_content = content;
|
||||
}
|
||||
|
||||
void actionText(String actionText) {
|
||||
_actionText = actionText;
|
||||
}
|
||||
|
||||
void addCancelDownloadActionClick(OnCancelDownloadActionClick onCancelDownloadActionClick) {
|
||||
_onCancelDownloadActionClick = onCancelDownloadActionClick;
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
return CupertinoAlertDialog(
|
||||
key: _key ?? Key('DownloadingFileBuilder'),
|
||||
title: Text(_title, style: TextStyle(fontSize: 17.0, color: Colors.black)),
|
||||
content: Padding(
|
||||
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: CupertinoActivityIndicator()),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
_content,
|
||||
style: TextStyle(fontSize: 13.0, color: Colors.black),
|
||||
softWrap: false,
|
||||
maxLines: 1)
|
||||
],
|
||||
),
|
||||
)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (_onCancelDownloadActionClick != null) {
|
||||
_onCancelDownloadActionClick!();
|
||||
}},
|
||||
child: Text(_actionText, style: TextStyle(fontSize: 17.0, color: AppColor.appColor)),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_inter
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/constants/email_constants.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/email_content_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/attachment_file.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/message_content.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:dio/dio.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';
|
||||
@@ -16,4 +17,12 @@ abstract class EmailRepository {
|
||||
String baseDownloadUrl,
|
||||
AccountRequest accountRequest
|
||||
);
|
||||
|
||||
Future<String> exportAttachment(
|
||||
Attachment attachment,
|
||||
AccountId accountId,
|
||||
String baseDownloadUrl,
|
||||
AccountRequest accountRequest,
|
||||
CancelToken cancelToken
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:core/core.dart';
|
||||
|
||||
class ExportAttachmentSuccess extends UIState {
|
||||
final String filePath;
|
||||
|
||||
ExportAttachmentSuccess(this.filePath);
|
||||
|
||||
@override
|
||||
List<Object> get props => [filePath];
|
||||
}
|
||||
|
||||
class ExportAttachmentFailure extends FeatureFailure {
|
||||
final exception;
|
||||
|
||||
ExportAttachmentFailure(this.exception);
|
||||
|
||||
@override
|
||||
List<Object> get props => [exception];
|
||||
}
|
||||
@@ -12,11 +12,11 @@ class DownloadAttachmentsInteractor {
|
||||
|
||||
DownloadAttachmentsInteractor(this.emailRepository, this.credentialRepository);
|
||||
|
||||
Future<Either<Failure, Success>> execute(
|
||||
Stream<Either<Failure, Success>> execute(
|
||||
List<Attachment> attachments,
|
||||
AccountId accountId,
|
||||
String baseDownloadUrl
|
||||
) async {
|
||||
) async* {
|
||||
try {
|
||||
final taskIds = await Future.wait(
|
||||
[credentialRepository.getUserName(), credentialRepository.getPassword()],
|
||||
@@ -30,9 +30,9 @@ class DownloadAttachmentsInteractor {
|
||||
accountRequest);
|
||||
});
|
||||
|
||||
return Right<Failure, Success>(DownloadAttachmentsSuccess(taskIds));
|
||||
yield Right<Failure, Success>(DownloadAttachmentsSuccess(taskIds));
|
||||
} catch (e) {
|
||||
return Left<Failure, Success>(DownloadAttachmentsFailure(e));
|
||||
yield Left<Failure, Success>(DownloadAttachmentsFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.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/export_attachment_state.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
|
||||
|
||||
class ExportAttachmentInteractor {
|
||||
final EmailRepository emailRepository;
|
||||
final CredentialRepository credentialRepository;
|
||||
|
||||
ExportAttachmentInteractor(this.emailRepository, this.credentialRepository);
|
||||
|
||||
Stream<Either<Failure, Success>> execute(
|
||||
Attachment attachment,
|
||||
AccountId accountId,
|
||||
String baseDownloadUrl,
|
||||
CancelToken cancelToken
|
||||
) async* {
|
||||
try {
|
||||
final filePath = await Future.wait(
|
||||
[credentialRepository.getUserName(), credentialRepository.getPassword()],
|
||||
eagerError: true
|
||||
).then((List responses) async {
|
||||
final accountRequest = AccountRequest(responses.first, responses.last);
|
||||
return await emailRepository.exportAttachment(
|
||||
attachment,
|
||||
accountId,
|
||||
baseDownloadUrl,
|
||||
accountRequest,
|
||||
cancelToken);
|
||||
});
|
||||
yield Right<Failure, Success>(ExportAttachmentSuccess(filePath));
|
||||
} catch (exception) {
|
||||
yield Left<Failure, Success>(ExportAttachmentFailure(exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,9 @@ dependencies:
|
||||
# permission_handler
|
||||
permission_handler: 8.1.6
|
||||
|
||||
# share
|
||||
share: 2.0.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user