TF-2644 Handle total size exceeded maximum size when drag & drop multiple file to composer
This commit is contained in:
@@ -10,15 +10,11 @@ class DownloadImageAsBase64Success extends UIState {
|
||||
final String base64Uri;
|
||||
final String cid;
|
||||
final FileInfo fileInfo;
|
||||
final bool fromFileShared;
|
||||
|
||||
DownloadImageAsBase64Success(
|
||||
this.base64Uri,
|
||||
this.cid,
|
||||
this.fileInfo,
|
||||
{
|
||||
this.fromFileShared = false
|
||||
}
|
||||
this.fileInfo
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -26,7 +22,6 @@ class DownloadImageAsBase64Success extends UIState {
|
||||
base64Uri,
|
||||
cid,
|
||||
fileInfo,
|
||||
fromFileShared,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +1,25 @@
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
|
||||
|
||||
class UploadAttachmentSuccess extends UIState {
|
||||
|
||||
final UploadAttachment uploadAttachment;
|
||||
final bool isInline;
|
||||
final bool fromFileShared;
|
||||
|
||||
UploadAttachmentSuccess(
|
||||
this.uploadAttachment,
|
||||
{
|
||||
this.isInline = false,
|
||||
this.fromFileShared = false,
|
||||
}
|
||||
);
|
||||
UploadAttachmentSuccess(this.uploadAttachment);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
uploadAttachment,
|
||||
isInline,
|
||||
fromFileShared,
|
||||
];
|
||||
List<Object?> get props => [uploadAttachment];
|
||||
}
|
||||
|
||||
class UploadAttachmentFailure extends FeatureFailure {
|
||||
final bool isInline;
|
||||
final bool fromFileShared;
|
||||
|
||||
UploadAttachmentFailure(
|
||||
dynamic exception,
|
||||
{
|
||||
this.isInline = false,
|
||||
this.fromFileShared = false,
|
||||
}
|
||||
) : super(exception: exception);
|
||||
final FileInfo fileInfo;
|
||||
|
||||
UploadAttachmentFailure(dynamic exception, this.fileInfo) : super(exception: exception);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
isInline,
|
||||
fromFileShared,
|
||||
...super.props
|
||||
];
|
||||
List<Object?> get props => [...super.props, fileInfo];
|
||||
}
|
||||
@@ -16,7 +16,6 @@ class DownloadImageAsBase64Interactor {
|
||||
{
|
||||
double? maxWidth,
|
||||
bool? compress,
|
||||
bool fromFileShared = false,
|
||||
}
|
||||
) async* {
|
||||
try {
|
||||
@@ -33,7 +32,6 @@ class DownloadImageAsBase64Interactor {
|
||||
result!,
|
||||
cid,
|
||||
fileInfo,
|
||||
fromFileShared: fromFileShared
|
||||
));
|
||||
} else {
|
||||
yield Left<Failure, Success>(DownloadImageAsBase64Failure(null));
|
||||
|
||||
@@ -15,8 +15,6 @@ class UploadAttachmentInteractor {
|
||||
FileInfo fileInfo,
|
||||
Uri uploadUri, {
|
||||
CancelToken? cancelToken,
|
||||
bool isInline = false,
|
||||
bool fromFileShared = false,
|
||||
}) async* {
|
||||
try {
|
||||
final uploadAttachment = await _composerRepository.uploadAttachment(
|
||||
@@ -24,17 +22,9 @@ class UploadAttachmentInteractor {
|
||||
uploadUri,
|
||||
cancelToken: cancelToken
|
||||
);
|
||||
yield Right<Failure, Success>(UploadAttachmentSuccess(
|
||||
uploadAttachment,
|
||||
isInline: isInline,
|
||||
fromFileShared: fromFileShared
|
||||
));
|
||||
yield Right<Failure, Success>(UploadAttachmentSuccess(uploadAttachment));
|
||||
} catch (e) {
|
||||
yield Left<Failure, Success>(UploadAttachmentFailure(
|
||||
e,
|
||||
isInline: isInline,
|
||||
fromFileShared: fromFileShared
|
||||
));
|
||||
yield Left<Failure, Success>(UploadAttachmentFailure(e, fileInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ import 'package:tmail_ui_user/features/upload/data/datasource/attachment_upload_
|
||||
import 'package:tmail_ui_user/features/upload/data/datasource_impl/attachment_upload_datasource_impl.dart';
|
||||
import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/usecases/local_image_picker_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.dart';
|
||||
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
||||
@@ -125,7 +126,7 @@ class ComposerBindings extends BaseBindings {
|
||||
Get.find<FileUtils>(),
|
||||
Get.find<CacheExceptionThrower>()));
|
||||
Get.lazyPut(() => RemoteServerSettingsDataSourceImpl(
|
||||
Get.find<ServerSettingsAPI>(),
|
||||
Get.find<ServerSettingsAPI>(),
|
||||
Get.find<RemoteExceptionThrower>()));
|
||||
}
|
||||
|
||||
@@ -182,6 +183,7 @@ class ComposerBindings extends BaseBindings {
|
||||
@override
|
||||
void bindingsInteractor() {
|
||||
Get.lazyPut(() => LocalFilePickerInteractor());
|
||||
Get.lazyPut(() => LocalImagePickerInteractor());
|
||||
Get.lazyPut(() => UploadAttachmentInteractor(Get.find<ComposerRepository>()));
|
||||
Get.lazyPut(() => SaveEmailAsDraftsInteractor(
|
||||
Get.find<EmailRepository>(),
|
||||
@@ -207,6 +209,7 @@ class ComposerBindings extends BaseBindings {
|
||||
Get.lazyPut(() => ComposerController(
|
||||
Get.find<DeviceInfoPlugin>(),
|
||||
Get.find<LocalFilePickerInteractor>(),
|
||||
Get.find<LocalImagePickerInteractor>(),
|
||||
Get.find<GetEmailContentInteractor>(),
|
||||
Get.find<GetAllIdentitiesInteractor>(),
|
||||
Get.find<UploadController>(),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@@ -51,8 +51,9 @@ import 'package:tmail_ui_user/features/composer/presentation/controller/rich_tex
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/file_upload_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/list_shared_media_file_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/drag_drog_file_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/save_to_draft_arguments.dart';
|
||||
@@ -69,6 +70,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_r
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_composer_cache_on_web_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/identity_extension.dart';
|
||||
@@ -79,18 +81,21 @@ import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.
|
||||
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/server_settings/domain/state/get_always_read_receipt_setting_state.dart';
|
||||
import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_always_read_receipt_setting_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/file_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_image_picker_state.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/usecases/local_image_picker_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
class ComposerController extends BaseController {
|
||||
class ComposerController extends BaseController with DragDropFileMixin {
|
||||
|
||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||
final richTextMobileTabletController = Get.find<RichTextMobileTabletController>();
|
||||
@@ -116,6 +121,7 @@ class ComposerController extends BaseController {
|
||||
final listFromIdentities = RxList<Identity>();
|
||||
|
||||
final LocalFilePickerInteractor _localFilePickerInteractor;
|
||||
final LocalImagePickerInteractor _localImagePickerInteractor;
|
||||
final DeviceInfoPlugin _deviceInfoPlugin;
|
||||
final GetEmailContentInteractor _getEmailContentInteractor;
|
||||
final GetAllIdentitiesInteractor _getAllIdentitiesInteractor;
|
||||
@@ -155,6 +161,12 @@ class ComposerController extends BaseController {
|
||||
FocusNode? bccAddressFocusNode;
|
||||
FocusNode? searchIdentitiesFocusNode;
|
||||
|
||||
StreamSubscription<html.Event>? _subscriptionOnBeforeUnload;
|
||||
StreamSubscription<html.Event>? _subscriptionOnDragEnter;
|
||||
StreamSubscription<html.Event>? _subscriptionOnDragOver;
|
||||
StreamSubscription<html.Event>? _subscriptionOnDragLeave;
|
||||
StreamSubscription<html.Event>? _subscriptionOnDrop;
|
||||
|
||||
final RichTextController keyboardRichTextController = RichTextController();
|
||||
|
||||
final ScrollController scrollController = ScrollController();
|
||||
@@ -178,6 +190,7 @@ class ComposerController extends BaseController {
|
||||
ComposerController(
|
||||
this._deviceInfoPlugin,
|
||||
this._localFilePickerInteractor,
|
||||
this._localImagePickerInteractor,
|
||||
this._getEmailContentInteractor,
|
||||
this._getAllIdentitiesInteractor,
|
||||
this.uploadController,
|
||||
@@ -201,7 +214,7 @@ class ComposerController extends BaseController {
|
||||
});
|
||||
} else {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_listenBrowserTabRefresh();
|
||||
_listenBrowserEventAction();
|
||||
});
|
||||
}
|
||||
_getAlwaysReadReceiptSetting();
|
||||
@@ -225,6 +238,11 @@ class ComposerController extends BaseController {
|
||||
emailContentsViewState.value = Right(UIClosedState());
|
||||
identitySelected.value = null;
|
||||
listFromIdentities.clear();
|
||||
_subscriptionOnBeforeUnload?.cancel();
|
||||
_subscriptionOnDragEnter?.cancel();
|
||||
_subscriptionOnDragOver?.cancel();
|
||||
_subscriptionOnDragLeave?.cancel();
|
||||
_subscriptionOnDrop?.cancel();
|
||||
if (PlatformInfo.isMobile) {
|
||||
FkUserAgent.release();
|
||||
}
|
||||
@@ -267,6 +285,8 @@ class ComposerController extends BaseController {
|
||||
emailContentsViewState.value = Right(success);
|
||||
} else if (success is LocalFilePickerSuccess) {
|
||||
_handlePickFileSuccess(success);
|
||||
} else if (success is LocalImagePickerSuccess) {
|
||||
_handlePickImageSuccess(success);
|
||||
} else if (success is GetEmailContentSuccess) {
|
||||
_getEmailContentSuccess(success);
|
||||
} else if (success is GetEmailContentFromCacheSuccess) {
|
||||
@@ -274,23 +294,11 @@ class ComposerController extends BaseController {
|
||||
} else if (success is GetAllIdentitiesSuccess) {
|
||||
_handleGetAllIdentitiesSuccess(success);
|
||||
} else if (success is DownloadImageAsBase64Success) {
|
||||
final inlineImage = InlineImage(fileInfo: success.fileInfo, base64Uri: success.base64Uri);
|
||||
if (PlatformInfo.isWeb) {
|
||||
richTextWebController.insertImage(
|
||||
InlineImage(
|
||||
ImageSource.local,
|
||||
fileInfo: success.fileInfo,
|
||||
cid: success.cid,
|
||||
base64Uri: success.base64Uri));
|
||||
richTextWebController.insertImage(inlineImage);
|
||||
} else {
|
||||
richTextMobileTabletController.insertImage(
|
||||
InlineImage(
|
||||
ImageSource.local,
|
||||
fileInfo: success.fileInfo,
|
||||
cid: success.cid,
|
||||
base64Uri: success.base64Uri
|
||||
),
|
||||
fromFileShare: success.fromFileShared
|
||||
);
|
||||
richTextMobileTabletController.insertImage(inlineImage);
|
||||
}
|
||||
maxWithEditor = null;
|
||||
} else if (success is GetAlwaysReadReceiptSettingSuccess) {
|
||||
@@ -301,8 +309,10 @@ class ComposerController extends BaseController {
|
||||
@override
|
||||
void handleFailureViewState(Failure failure) {
|
||||
super.handleFailureViewState(failure);
|
||||
if (failure is LocalFilePickerFailure || failure is LocalFilePickerCancel) {
|
||||
_pickFileFailure(failure);
|
||||
if (failure is LocalFilePickerFailure) {
|
||||
_handlePickFileFailure(failure);
|
||||
} else if (failure is LocalImagePickerFailure) {
|
||||
_handlePickImageFailure(failure);
|
||||
} else if (failure is GetEmailContentFailure ||
|
||||
failure is TransformHtmlEmailContentFailure) {
|
||||
emailContentsViewState.value = Left(failure);
|
||||
@@ -358,8 +368,9 @@ class ComposerController extends BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
void _listenBrowserTabRefresh() {
|
||||
html.window.onBeforeUnload.listen((event) async {
|
||||
void _listenBrowserEventAction() {
|
||||
log('ComposerController::_listenBrowserEventAction:');
|
||||
_subscriptionOnBeforeUnload = html.window.onBeforeUnload.listen((event) async {
|
||||
final userProfile = mailboxDashBoardController.userProfile.value;
|
||||
_removeComposerCacheOnWebInteractor.execute();
|
||||
if (userProfile != null) {
|
||||
@@ -367,6 +378,22 @@ class ComposerController extends BaseController {
|
||||
_saveComposerCacheOnWebInteractor.execute(draftEmail);
|
||||
}
|
||||
});
|
||||
|
||||
_subscriptionOnDragEnter = html.window.onDragEnter.listen((event) {
|
||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
||||
});
|
||||
|
||||
_subscriptionOnDragOver = html.window.onDragOver.listen((event) {
|
||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
||||
});
|
||||
|
||||
_subscriptionOnDragLeave = html.window.onDragLeave.listen((event) {
|
||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.inActive;
|
||||
});
|
||||
|
||||
_subscriptionOnDrop = html.window.onDrop.listen((event) {
|
||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.inActive;
|
||||
});
|
||||
}
|
||||
|
||||
void _scrollControllerEmailAddressListener() {
|
||||
@@ -1064,29 +1091,45 @@ class ComposerController extends BaseController {
|
||||
consumeState(_localFilePickerInteractor.execute(fileType: fileType));
|
||||
}
|
||||
|
||||
void _pickFileFailure(Failure failure) {
|
||||
if (failure is LocalFilePickerFailure) {
|
||||
if (currentOverlayContext != null && currentContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments);
|
||||
}
|
||||
void _handlePickFileFailure(LocalFilePickerFailure failure) {
|
||||
if (currentOverlayContext != null && currentContext != null && failure.exception is! PickFileCanceledException) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).thisFileCannotBePicked);
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePickImageFailure(LocalImagePickerFailure failure) {
|
||||
if (currentOverlayContext != null && currentContext != null && failure.exception is! PickFileCanceledException) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).cannotSelectThisImage);
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePickFileSuccess(LocalFilePickerSuccess success) {
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: success.pickedFiles.totalSize,
|
||||
callbackAction: () => _uploadAttachmentsAction(success.pickedFiles)
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: success.pickedFiles)
|
||||
);
|
||||
}
|
||||
|
||||
void _uploadAttachmentsAction(List<FileInfo> pickedFiles) {
|
||||
void _handlePickImageSuccess(LocalImagePickerSuccess success) {
|
||||
uploadController.validateTotalSizeInlineAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: success.fileInfo.fileSize,
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: [success.fileInfo.withInline()])
|
||||
);
|
||||
}
|
||||
|
||||
void _uploadAttachmentsAction({required List<FileInfo> pickedFiles}) {
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
final accountId = mailboxDashBoardController.accountId.value;
|
||||
if (session != null && accountId != null) {
|
||||
final uploadUri = session.getUploadUri(accountId, jmapUrl: _dynamicUrlInterceptors.jmapUrl);
|
||||
uploadController.justUploadAttachmentsAction(pickedFiles, uploadUri);
|
||||
uploadController.justUploadAttachmentsAction(
|
||||
uploadFiles: pickedFiles,
|
||||
uploadUri: uploadUri,
|
||||
);
|
||||
} else {
|
||||
log('ComposerController::_uploadAttachmentsAction: SESSION OR ACCOUNT_ID is NULL');
|
||||
}
|
||||
@@ -1237,71 +1280,17 @@ class ComposerController extends BaseController {
|
||||
);
|
||||
}
|
||||
|
||||
File _covertSharedMediaFileToFile(SharedMediaFile sharedMediaFile) {
|
||||
return File(
|
||||
Platform.isIOS
|
||||
? sharedMediaFile.type == SharedMediaType.FILE
|
||||
? sharedMediaFile.path.toString().replaceAll('file:/', '').replaceAll('%20', ' ')
|
||||
: sharedMediaFile.path.toString().replaceAll('%20', ' ')
|
||||
: sharedMediaFile.path,
|
||||
);
|
||||
}
|
||||
|
||||
FileInfo _covertFileToFileInfo(File file) {
|
||||
return FileInfo(
|
||||
file.path.split('/').last,
|
||||
file.path,
|
||||
file.existsSync() ? file.lengthSync() : 0,
|
||||
);
|
||||
}
|
||||
|
||||
List<InlineImage> covertListSharedMediaFileToInlineImage(List<SharedMediaFile> value) {
|
||||
List<File> newFiles = List.empty(growable: true);
|
||||
if (value.isNotEmpty) {
|
||||
for (var element in value) {
|
||||
newFiles.add(_covertSharedMediaFileToFile(element));
|
||||
}
|
||||
}
|
||||
|
||||
final List<InlineImage> listInlineImage = newFiles.map(
|
||||
(e) => InlineImage(
|
||||
ImageSource.local,
|
||||
fileInfo: _covertFileToFileInfo(e),
|
||||
)
|
||||
).toList();
|
||||
return listInlineImage;
|
||||
}
|
||||
|
||||
List<FileInfo> covertListSharedMediaFileToFileInfo(List<SharedMediaFile> value) {
|
||||
List<File> newFiles = List.empty(growable: true);
|
||||
if (value.isNotEmpty) {
|
||||
for (var element in value) {
|
||||
newFiles.add(_covertSharedMediaFileToFile(element));
|
||||
}
|
||||
}
|
||||
|
||||
final List<FileInfo> listFileInfo = newFiles.map(
|
||||
(e) => _covertFileToFileInfo(e),
|
||||
).toList();
|
||||
return listFileInfo;
|
||||
}
|
||||
|
||||
void _addAttachmentFromFileShare(List<SharedMediaFile> listSharedMediaFile) {
|
||||
final listImageSharedMediaFile = listSharedMediaFile.where((element) => element.type == SharedMediaType.IMAGE);
|
||||
final listFileAttachmentSharedMediaFile = listSharedMediaFile.where((element) => element.type != SharedMediaType.IMAGE);
|
||||
if (listImageSharedMediaFile.isNotEmpty) {
|
||||
final listInlineImage = covertListSharedMediaFileToInlineImage(listSharedMediaFile);
|
||||
for (var e in listInlineImage) {
|
||||
_uploadInlineAttachmentsAction(e.fileInfo!, fromFileShared: true);
|
||||
}
|
||||
}
|
||||
if (listFileAttachmentSharedMediaFile.isNotEmpty) {
|
||||
final listFileInfo = covertListSharedMediaFileToFileInfo(listSharedMediaFile);
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: listFileInfo.totalSize,
|
||||
callbackAction: () => _uploadAttachmentsAction(listFileInfo)
|
||||
);
|
||||
}
|
||||
final listFileInfo = listSharedMediaFile.toListFileInfo(isShared: true);
|
||||
|
||||
final tupleListFileInfo = partition(listFileInfo, (fileInfo) => fileInfo.isInline == true);
|
||||
final listAttachments = tupleListFileInfo.value2;
|
||||
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: listFileInfo.totalSize,
|
||||
totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
|
||||
);
|
||||
}
|
||||
|
||||
void _getEmailContentFromSendingEmail(SendingEmail sendingEmail) {
|
||||
@@ -1712,80 +1701,8 @@ class ComposerController extends BaseController {
|
||||
} else {
|
||||
maxWithEditor = maxWith - 120;
|
||||
}
|
||||
final inlineImage = await _selectFromFile();
|
||||
if (inlineImage != null) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
_insertImageOnWeb(inlineImage);
|
||||
} else {
|
||||
_insertImageOnMobileAndTablet(inlineImage);
|
||||
}
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
appToast.showToastErrorMessage(context, AppLocalizations.of(context).cannotSelectThisImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<InlineImage?> _selectFromFile() async {
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
withData: PlatformInfo.isWeb
|
||||
);
|
||||
if (filePickerResult?.files.isNotEmpty == true) {
|
||||
PlatformFile platformFile = filePickerResult!.files.first;
|
||||
final fileSelected = FileInfo(
|
||||
platformFile.name,
|
||||
PlatformInfo.isWeb ? '' : platformFile.path ?? '',
|
||||
platformFile.size,
|
||||
bytes: PlatformInfo.isWeb ? platformFile.bytes : null,
|
||||
);
|
||||
return InlineImage(ImageSource.local, fileInfo: fileSelected);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void _insertImageOnWeb(InlineImage inlineImage) {
|
||||
if (inlineImage.source == ImageSource.local) {
|
||||
_uploadInlineAttachmentsAction(inlineImage.fileInfo!);
|
||||
} else {
|
||||
richTextWebController.insertImage(inlineImage);
|
||||
}
|
||||
}
|
||||
|
||||
void _insertImageOnMobileAndTablet(InlineImage inlineImage) {
|
||||
if (inlineImage.source == ImageSource.local) {
|
||||
_uploadInlineAttachmentsAction(inlineImage.fileInfo!);
|
||||
} else {
|
||||
richTextMobileTabletController.insertImage(inlineImage);
|
||||
}
|
||||
}
|
||||
|
||||
void _uploadInlineAttachmentsAction(FileInfo pickedFile, {bool fromFileShared = false}) async {
|
||||
if (!uploadController.isExceededMaxSizeAttachmentsPerEmail(totalSizePreparedFiles: pickedFile.fileSize)) {
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
final accountId = mailboxDashBoardController.accountId.value;
|
||||
if (session != null && accountId != null) {
|
||||
final uploadUri = session.getUploadUri(accountId, jmapUrl: _dynamicUrlInterceptors.jmapUrl);
|
||||
uploadController.uploadFileAction(
|
||||
pickedFile,
|
||||
uploadUri,
|
||||
isInline: true,
|
||||
fromFileShared: fromFileShared
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (currentContext != null) {
|
||||
showConfirmDialogAction(
|
||||
currentContext!,
|
||||
AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size(
|
||||
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)),
|
||||
AppLocalizations.of(currentContext!).got_it,
|
||||
onConfirmAction: () => {isSendEmailLoading.value = false},
|
||||
title: AppLocalizations.of(currentContext!).maximum_files_size,
|
||||
hasCancelButton: false);
|
||||
}
|
||||
}
|
||||
consumeState(_localImagePickerInteractor.execute());
|
||||
}
|
||||
|
||||
void _handleUploadInlineSuccess(SuccessAttachmentUploadState uploadState) {
|
||||
@@ -1802,7 +1719,6 @@ class ComposerController extends BaseController {
|
||||
uploadState.attachment.cid!,
|
||||
uploadState.fileInfo,
|
||||
maxWidth: maxWithEditor,
|
||||
fromFileShared: uploadState.fromFileShared,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1896,6 +1812,7 @@ class ComposerController extends BaseController {
|
||||
log('ComposerController::handleInitHtmlEditorWeb:');
|
||||
_isEmailBodyLoaded = true;
|
||||
richTextWebController.editorController.setFullScreen();
|
||||
richTextWebController.editorController.setOnDragDropEvent();
|
||||
onChangeTextEditorWeb(initContent);
|
||||
richTextWebController.setEnableCodeView();
|
||||
if (identitySelected.value == null) {
|
||||
@@ -1921,10 +1838,25 @@ class ComposerController extends BaseController {
|
||||
|
||||
void handleImageUploadSuccess (
|
||||
BuildContext context,
|
||||
web_html_editor.FileUpload fileUpload
|
||||
List<web_html_editor.FileUpload> listFileUpload
|
||||
) async {
|
||||
log('ComposerController::handleImageUploadSuccess:NAME: ${fileUpload.name} | TYPE: ${fileUpload.type} | SIZE: ${fileUpload.size}');
|
||||
if (fileUpload.base64 == null) {
|
||||
log('ComposerController::handleImageUploadSuccess: COUNT_FILE_UPLOADED = ${listFileUpload.length}');
|
||||
List<FileInfo> listFileInfo = [];
|
||||
|
||||
await Future.forEach(listFileUpload, (fileUpload) async {
|
||||
if (fileUpload.base64?.isNotEmpty == true) {
|
||||
final fileInfo = await fileUpload.toFileInfo();
|
||||
if (fileInfo != null) {
|
||||
if (fileInfo.mimeType.startsWith(MediaTypeExtension.imageType) == true) {
|
||||
listFileInfo.add(fileInfo.withInline());
|
||||
} else {
|
||||
listFileInfo.add(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (listFileInfo.isEmpty && context.mounted) {
|
||||
appToast.showToastErrorMessage(
|
||||
context,
|
||||
AppLocalizations.of(context).can_not_upload_this_file_as_attachments
|
||||
@@ -1932,49 +1864,30 @@ class ComposerController extends BaseController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileUpload.type?.startsWith(Constant.imageType) == true) {
|
||||
final fileInfo = await fileUpload.toFileInfo();
|
||||
if (fileInfo != null) {
|
||||
_uploadInlineAttachmentsAction(fileInfo);
|
||||
} else if (context.mounted) {
|
||||
appToast.showToastErrorMessage(
|
||||
context,
|
||||
AppLocalizations.of(context).can_not_upload_this_file_as_attachments
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final fileInfo = await fileUpload.toFileInfo();
|
||||
if (fileInfo != null) {
|
||||
_addAttachmentFromDragAndDrop(fileInfo: fileInfo);
|
||||
} else if (context.mounted) {
|
||||
appToast.showToastErrorMessage(
|
||||
context,
|
||||
AppLocalizations.of(context).can_not_upload_this_file_as_attachments
|
||||
);
|
||||
}
|
||||
}
|
||||
final listAttachments = listFileInfo
|
||||
.where((fileInfo) => fileInfo.isInline != true)
|
||||
.toList();
|
||||
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: listFileInfo.totalSize,
|
||||
totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
|
||||
);
|
||||
}
|
||||
|
||||
void handleImageUploadFailure({
|
||||
required BuildContext context,
|
||||
required web_html_editor.UploadError uploadError,
|
||||
web_html_editor.FileUpload? fileUpload,
|
||||
List<web_html_editor.FileUpload>? listFileUpload,
|
||||
String? base64Str,
|
||||
}) {
|
||||
logError('ComposerController::handleImageUploadFailure:fileUpload: $fileUpload | uploadError: $uploadError');
|
||||
logError('ComposerController::handleImageUploadFailure: COUNT_FILE_FAILED = ${listFileUpload?.length} | ERROR = $uploadError');
|
||||
appToast.showToastErrorMessage(
|
||||
context,
|
||||
'${AppLocalizations.of(context).can_not_upload_this_file_as_attachments}. (${uploadError.name})'
|
||||
);
|
||||
}
|
||||
|
||||
void _addAttachmentFromDragAndDrop({required FileInfo fileInfo}) {
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: fileInfo.fileSize,
|
||||
callbackAction: () => _uploadAttachmentsAction([fileInfo])
|
||||
);
|
||||
}
|
||||
|
||||
FocusNode? getNextFocusOfToEmailAddress() {
|
||||
if (ccRecipientState.value == PrefixRecipientState.enabled) {
|
||||
return ccAddressFocusNode;
|
||||
@@ -2066,11 +1979,11 @@ class ComposerController extends BaseController {
|
||||
_updateStatusEmailSendButton();
|
||||
}
|
||||
|
||||
void addAttachmentWhenDragFromOtherEmail(Attachment attachment) {
|
||||
log('ComposerController::addAttachmentWhenDragFromOtherEmail: attachment = $attachment');
|
||||
void onAttachmentDropZoneListener(Attachment attachment) {
|
||||
log('ComposerController::onAttachmentDropZoneListener: attachment = $attachment');
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: attachment.size?.value ?? 0,
|
||||
callbackAction: () => uploadController.initializeUploadAttachments([attachment])
|
||||
onValidationSuccess: () => uploadController.initializeUploadAttachments([attachment])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2144,4 +2057,33 @@ class ComposerController extends BaseController {
|
||||
consumeState(_getAlwaysReadReceiptSettingInteractor.execute(accountId));
|
||||
}
|
||||
}
|
||||
|
||||
void handleOnDragEnterHtmlEditorWeb() {
|
||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
||||
}
|
||||
|
||||
void onLocalFileDropZoneListener({
|
||||
required BuildContext context,
|
||||
required DropDoneDetails details
|
||||
}) async {
|
||||
final listFileInfo = await onDragDone(context: context, details: details);
|
||||
|
||||
if (listFileInfo.isEmpty && context.mounted) {
|
||||
appToast.showToastErrorMessage(
|
||||
context,
|
||||
AppLocalizations.of(context).can_not_upload_this_file_as_attachments
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final listAttachments = listFileInfo
|
||||
.where((fileInfo) => fileInfo.isInline != true)
|
||||
.toList();
|
||||
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: listFileInfo.totalSize,
|
||||
totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,11 @@ import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_composer_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_drop_zone_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/desktop_app_bar_composer_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/drop_zone_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/local_file_drop_zone_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
@@ -157,72 +158,110 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
margin: ComposerStyle.mobileSubjectMargin,
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: ComposerStyle.mobileEditorPadding,
|
||||
child: Obx(() => WebEditorView(
|
||||
editorController: controller.richTextWebController.editorController,
|
||||
arguments: controller.composerArguments.value,
|
||||
contentViewState: controller.emailContentsViewState.value,
|
||||
currentWebContent: controller.textEditorWeb,
|
||||
onInitial: controller.handleInitHtmlEditorWeb,
|
||||
onChangeContent: controller.onChangeTextEditorWeb,
|
||||
onFocus: controller.handleOnFocusHtmlEditorWeb,
|
||||
onUnFocus: controller.handleOnUnFocusHtmlEditorWeb,
|
||||
onMouseDown: controller.handleOnMouseDownHtmlEditorWeb,
|
||||
onEditorSettings: controller.richTextWebController.onEditorSettingsChange,
|
||||
onImageUploadSuccessAction: (fileUpload) => controller.handleImageUploadSuccess(context, fileUpload),
|
||||
onImageUploadFailureAction: (fileUpload, base64Str, uploadError) {
|
||||
return controller.handleImageUploadFailure(
|
||||
context: context,
|
||||
uploadError: uploadError,
|
||||
fileUpload: fileUpload,
|
||||
base64Str: base64Str,
|
||||
);
|
||||
},
|
||||
onEditorTextSizeChanged: controller.richTextWebController.onEditorTextSizeChanged,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
)),
|
||||
),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Obx(() => InsertImageLoadingBarWidget(
|
||||
uploadInlineViewState: controller.uploadController.uploadInlineViewState.value,
|
||||
viewState: controller.viewState.value,
|
||||
padding: ComposerStyle.insertImageLoadingBarPadding,
|
||||
)),
|
||||
),
|
||||
],
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraintsEditor) {
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: ComposerStyle.mobileEditorPadding,
|
||||
child: Obx(() => WebEditorView(
|
||||
editorController: controller.richTextWebController.editorController,
|
||||
arguments: controller.composerArguments.value,
|
||||
contentViewState: controller.emailContentsViewState.value,
|
||||
currentWebContent: controller.textEditorWeb,
|
||||
onInitial: controller.handleInitHtmlEditorWeb,
|
||||
onChangeContent: controller.onChangeTextEditorWeb,
|
||||
onFocus: controller.handleOnFocusHtmlEditorWeb,
|
||||
onUnFocus: controller.handleOnUnFocusHtmlEditorWeb,
|
||||
onMouseDown: controller.handleOnMouseDownHtmlEditorWeb,
|
||||
onEditorSettings: controller.richTextWebController.onEditorSettingsChange,
|
||||
onEditorTextSizeChanged: controller.richTextWebController.onEditorTextSizeChanged,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
)),
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.uploadController.listUploadAttachments.isNotEmpty) {
|
||||
return AttachmentComposerWidget(
|
||||
listFileUploaded: controller.uploadController.listUploadAttachments,
|
||||
isCollapsed: controller.isAttachmentCollapsed,
|
||||
onDeleteAttachmentAction: (fileState) => controller.deleteAttachmentUploaded(fileState.uploadTaskId),
|
||||
onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.richTextWebController.isFormattingOptionsEnabled) {
|
||||
return ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController,
|
||||
padding: ComposerStyle.richToolbarPadding,
|
||||
decoration: const BoxDecoration(
|
||||
color: ComposerStyle.richToolbarColor,
|
||||
boxShadow: ComposerStyle.richToolbarShadow
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Obx(() => InsertImageLoadingBarWidget(
|
||||
uploadInlineViewState: controller.uploadController.uploadInlineViewState.value,
|
||||
viewState: controller.viewState.value,
|
||||
padding: ComposerStyle.insertImageLoadingBarPadding,
|
||||
)),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) {
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: AttachmentDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener,
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) {
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: LocalFileDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
onLocalFileDropZoneListener: (details) =>
|
||||
controller.onLocalFileDropZoneListener(
|
||||
context: context,
|
||||
details: details
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.uploadController.listUploadAttachments.isNotEmpty) {
|
||||
return AttachmentComposerWidget(
|
||||
listFileUploaded: controller.uploadController.listUploadAttachments,
|
||||
isCollapsed: controller.isAttachmentCollapsed,
|
||||
onDeleteAttachmentAction: (fileState) => controller.deleteAttachmentUploaded(fileState.uploadTaskId),
|
||||
onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.richTextWebController.isFormattingOptionsEnabled) {
|
||||
return ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController,
|
||||
padding: ComposerStyle.richToolbarPadding,
|
||||
decoration: const BoxDecoration(
|
||||
color: ComposerStyle.richToolbarColor,
|
||||
boxShadow: ComposerStyle.richToolbarShadow
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
]
|
||||
),
|
||||
);
|
||||
@@ -336,123 +375,149 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
margin: ComposerStyle.desktopSubjectMargin,
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ComposerStyle.borderColor,
|
||||
width: 1
|
||||
)
|
||||
),
|
||||
color: ComposerStyle.backgroundEditorColor
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: ComposerStyle.desktopEditorPadding,
|
||||
child: Obx(() {
|
||||
return Stack(
|
||||
children: [
|
||||
WebEditorView(
|
||||
editorController: controller.richTextWebController.editorController,
|
||||
arguments: controller.composerArguments.value,
|
||||
contentViewState: controller.emailContentsViewState.value,
|
||||
currentWebContent: controller.textEditorWeb,
|
||||
onInitial: controller.handleInitHtmlEditorWeb,
|
||||
onChangeContent: controller.onChangeTextEditorWeb,
|
||||
onFocus: controller.handleOnFocusHtmlEditorWeb,
|
||||
onUnFocus: controller.handleOnUnFocusHtmlEditorWeb,
|
||||
onMouseDown: controller.handleOnMouseDownHtmlEditorWeb,
|
||||
onEditorSettings: controller.richTextWebController.onEditorSettingsChange,
|
||||
onImageUploadSuccessAction: (fileUpload) => controller.handleImageUploadSuccess(context, fileUpload),
|
||||
onImageUploadFailureAction: (fileUpload, base64Str, uploadError) {
|
||||
return controller.handleImageUploadFailure(
|
||||
context: context,
|
||||
uploadError: uploadError,
|
||||
fileUpload: fileUpload,
|
||||
base64Str: base64Str,
|
||||
);
|
||||
},
|
||||
onEditorTextSizeChanged: controller.richTextWebController.onEditorTextSizeChanged,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
if (controller.mailboxDashBoardController.isDraggableAppActive)
|
||||
PointerInterceptor(
|
||||
child: DropZoneWidget(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
addAttachmentFromDropZone: controller.addAttachmentWhenDragFromOtherEmail,
|
||||
)
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.uploadController.listUploadAttachments.isNotEmpty) {
|
||||
return AttachmentComposerWidget(
|
||||
listFileUploaded: controller.uploadController.listUploadAttachments,
|
||||
isCollapsed: controller.isAttachmentCollapsed,
|
||||
onDeleteAttachmentAction: (fileState) => controller.deleteAttachmentUploaded(fileState.uploadTaskId),
|
||||
onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.richTextWebController.isFormattingOptionsEnabled) {
|
||||
return ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController,
|
||||
padding: ComposerStyle.richToolbarPadding,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraintsEditor) {
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: ComposerStyle.richToolbarColor,
|
||||
boxShadow: ComposerStyle.richToolbarShadow
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ComposerStyle.borderColor,
|
||||
width: 1
|
||||
)
|
||||
),
|
||||
color: ComposerStyle.backgroundEditorColor
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Obx(() => InsertImageLoadingBarWidget(
|
||||
uploadInlineViewState: controller.uploadController.uploadInlineViewState.value,
|
||||
viewState: controller.viewState.value,
|
||||
padding: ComposerStyle.insertImageLoadingBarPadding,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: ComposerStyle.desktopEditorPadding,
|
||||
child: Obx(() {
|
||||
return WebEditorView(
|
||||
editorController: controller.richTextWebController.editorController,
|
||||
arguments: controller.composerArguments.value,
|
||||
contentViewState: controller.emailContentsViewState.value,
|
||||
currentWebContent: controller.textEditorWeb,
|
||||
onInitial: controller.handleInitHtmlEditorWeb,
|
||||
onChangeContent: controller.onChangeTextEditorWeb,
|
||||
onFocus: controller.handleOnFocusHtmlEditorWeb,
|
||||
onUnFocus: controller.handleOnUnFocusHtmlEditorWeb,
|
||||
onMouseDown: controller.handleOnMouseDownHtmlEditorWeb,
|
||||
onEditorSettings: controller.richTextWebController.onEditorSettingsChange,
|
||||
onEditorTextSizeChanged: controller.richTextWebController.onEditorTextSizeChanged,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.uploadController.listUploadAttachments.isNotEmpty) {
|
||||
return AttachmentComposerWidget(
|
||||
listFileUploaded: controller.uploadController.listUploadAttachments,
|
||||
isCollapsed: controller.isAttachmentCollapsed,
|
||||
onDeleteAttachmentAction: (fileState) => controller.deleteAttachmentUploaded(fileState.uploadTaskId),
|
||||
onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.richTextWebController.isFormattingOptionsEnabled) {
|
||||
return ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController,
|
||||
padding: ComposerStyle.richToolbarPadding,
|
||||
decoration: const BoxDecoration(
|
||||
color: ComposerStyle.richToolbarColor,
|
||||
boxShadow: ComposerStyle.richToolbarShadow
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(() => BottomBarComposerWidget(
|
||||
isCodeViewEnabled: controller.richTextWebController.codeViewEnabled,
|
||||
isFormattingOptionsEnabled: controller.richTextWebController.isFormattingOptionsEnabled,
|
||||
openRichToolbarAction: controller.richTextWebController.toggleFormattingOptions,
|
||||
attachFileAction: () => controller.openFilePickerByType(context, FileType.any),
|
||||
insertImageAction: () => controller.insertImage(context, constraints.maxWidth),
|
||||
showCodeViewAction: controller.richTextWebController.toggleCodeView,
|
||||
deleteComposerAction: () => controller.handleClickDeleteComposer(context),
|
||||
saveToDraftAction: () => controller.saveToDraftAction(context),
|
||||
sendMessageAction: () => controller.validateInformationBeforeSending(context),
|
||||
requestReadReceiptAction: (position) {
|
||||
controller.openPopupMenuAction(
|
||||
context,
|
||||
position,
|
||||
_createReadReceiptPopupItems(context),
|
||||
radius: ComposerStyle.popupMenuRadius
|
||||
);
|
||||
},
|
||||
isSending: controller.isSendEmailLoading.value,
|
||||
)),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Obx(() => InsertImageLoadingBarWidget(
|
||||
uploadInlineViewState: controller.uploadController.uploadInlineViewState.value,
|
||||
viewState: controller.viewState.value,
|
||||
padding: ComposerStyle.insertImageLoadingBarPadding,
|
||||
)),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) {
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: AttachmentDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener,
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) {
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: LocalFileDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
onLocalFileDropZoneListener: (details) =>
|
||||
controller.onLocalFileDropZoneListener(
|
||||
context: context,
|
||||
details: details
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
Obx(() => BottomBarComposerWidget(
|
||||
isCodeViewEnabled: controller.richTextWebController.codeViewEnabled,
|
||||
isFormattingOptionsEnabled: controller.richTextWebController.isFormattingOptionsEnabled,
|
||||
openRichToolbarAction: controller.richTextWebController.toggleFormattingOptions,
|
||||
attachFileAction: () => controller.openFilePickerByType(context, FileType.any),
|
||||
insertImageAction: () => controller.insertImage(context, constraints.maxWidth),
|
||||
showCodeViewAction: controller.richTextWebController.toggleCodeView,
|
||||
deleteComposerAction: () => controller.handleClickDeleteComposer(context),
|
||||
saveToDraftAction: () => controller.saveToDraftAction(context),
|
||||
sendMessageAction: () => controller.validateInformationBeforeSending(context),
|
||||
requestReadReceiptAction: (position) {
|
||||
controller.openPopupMenuAction(
|
||||
context,
|
||||
position,
|
||||
_createReadReceiptPopupItems(context),
|
||||
radius: ComposerStyle.popupMenuRadius
|
||||
);
|
||||
},
|
||||
isSending: controller.isSendEmailLoading.value,
|
||||
)),
|
||||
]),
|
||||
);
|
||||
},
|
||||
@@ -567,87 +632,119 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
margin: ComposerStyle.tabletSubjectMargin,
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ComposerStyle.borderColor,
|
||||
width: 1
|
||||
)
|
||||
),
|
||||
color: ComposerStyle.backgroundEditorColor
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraintsEditor) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ComposerStyle.borderColor,
|
||||
width: 1
|
||||
)
|
||||
),
|
||||
color: ComposerStyle.backgroundEditorColor
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: ComposerStyle.tabletEditorPadding,
|
||||
child: Obx(() => WebEditorView(
|
||||
editorController: controller.richTextWebController.editorController,
|
||||
arguments: controller.composerArguments.value,
|
||||
contentViewState: controller.emailContentsViewState.value,
|
||||
currentWebContent: controller.textEditorWeb,
|
||||
onInitial: controller.handleInitHtmlEditorWeb,
|
||||
onChangeContent: controller.onChangeTextEditorWeb,
|
||||
onFocus: controller.handleOnFocusHtmlEditorWeb,
|
||||
onUnFocus: controller.handleOnUnFocusHtmlEditorWeb,
|
||||
onMouseDown: controller.handleOnMouseDownHtmlEditorWeb,
|
||||
onEditorSettings: controller.richTextWebController.onEditorSettingsChange,
|
||||
onImageUploadSuccessAction: (fileUpload) => controller.handleImageUploadSuccess(context, fileUpload),
|
||||
onImageUploadFailureAction: (fileUpload, base64Str, uploadError) {
|
||||
return controller.handleImageUploadFailure(
|
||||
context: context,
|
||||
uploadError: uploadError,
|
||||
fileUpload: fileUpload,
|
||||
base64Str: base64Str,
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: ComposerStyle.tabletEditorPadding,
|
||||
child: Obx(() => WebEditorView(
|
||||
editorController: controller.richTextWebController.editorController,
|
||||
arguments: controller.composerArguments.value,
|
||||
contentViewState: controller.emailContentsViewState.value,
|
||||
currentWebContent: controller.textEditorWeb,
|
||||
onInitial: controller.handleInitHtmlEditorWeb,
|
||||
onChangeContent: controller.onChangeTextEditorWeb,
|
||||
onFocus: controller.handleOnFocusHtmlEditorWeb,
|
||||
onUnFocus: controller.handleOnUnFocusHtmlEditorWeb,
|
||||
onMouseDown: controller.handleOnMouseDownHtmlEditorWeb,
|
||||
onEditorSettings: controller.richTextWebController.onEditorSettingsChange,
|
||||
onEditorTextSizeChanged: controller.richTextWebController.onEditorTextSizeChanged,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
)),
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.uploadController.listUploadAttachments.isNotEmpty) {
|
||||
return AttachmentComposerWidget(
|
||||
listFileUploaded: controller.uploadController.listUploadAttachments,
|
||||
isCollapsed: controller.isAttachmentCollapsed,
|
||||
onDeleteAttachmentAction: (fileState) => controller.deleteAttachmentUploaded(fileState.uploadTaskId),
|
||||
onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed,
|
||||
);
|
||||
},
|
||||
onEditorTextSizeChanged: controller.richTextWebController.onEditorTextSizeChanged,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
)),
|
||||
),
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.richTextWebController.isFormattingOptionsEnabled) {
|
||||
return ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController,
|
||||
padding: ComposerStyle.richToolbarPadding,
|
||||
decoration: const BoxDecoration(
|
||||
color: ComposerStyle.richToolbarColor,
|
||||
boxShadow: ComposerStyle.richToolbarShadow
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Obx(() => InsertImageLoadingBarWidget(
|
||||
uploadInlineViewState: controller.uploadController.uploadInlineViewState.value,
|
||||
viewState: controller.viewState.value,
|
||||
padding: ComposerStyle.insertImageLoadingBarPadding,
|
||||
)),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.uploadController.listUploadAttachments.isNotEmpty) {
|
||||
return AttachmentComposerWidget(
|
||||
listFileUploaded: controller.uploadController.listUploadAttachments,
|
||||
isCollapsed: controller.isAttachmentCollapsed,
|
||||
onDeleteAttachmentAction: (fileState) => controller.deleteAttachmentUploaded(fileState.uploadTaskId),
|
||||
onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed,
|
||||
if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) {
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: AttachmentDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener,
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (controller.richTextWebController.isFormattingOptionsEnabled) {
|
||||
return ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController,
|
||||
padding: ComposerStyle.richToolbarPadding,
|
||||
decoration: const BoxDecoration(
|
||||
color: ComposerStyle.richToolbarColor,
|
||||
boxShadow: ComposerStyle.richToolbarShadow
|
||||
if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) {
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: LocalFileDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
onLocalFileDropZoneListener: (details) =>
|
||||
controller.onLocalFileDropZoneListener(
|
||||
context: context,
|
||||
details: details
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
}),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: Obx(() => InsertImageLoadingBarWidget(
|
||||
uploadInlineViewState: controller.uploadController.uploadInlineViewState.value,
|
||||
viewState: controller.viewState.value,
|
||||
padding: ComposerStyle.insertImageLoadingBarPadding,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
Obx(() => BottomBarComposerWidget(
|
||||
|
||||
+5
-15
@@ -6,27 +6,17 @@ import 'package:file_picker/file_picker.dart';
|
||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/controller/base_rich_text_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
|
||||
|
||||
class RichTextMobileTabletController extends BaseRichTextController {
|
||||
HtmlEditorApi? htmlEditorApi;
|
||||
|
||||
void insertImage(
|
||||
InlineImage image,
|
||||
{
|
||||
double? maxWithEditor,
|
||||
bool fromFileShare = false
|
||||
void insertImage(InlineImage inlineImage) async {
|
||||
if (inlineImage.fileInfo.isShared == true) {
|
||||
await htmlEditorApi?.moveCursorAtLastNode();
|
||||
}
|
||||
) async {
|
||||
log('RichTextMobileTabletController::insertImage(): $image | maxWithEditor: $maxWithEditor | $fromFileShare');
|
||||
if (image.source == ImageSource.network) {
|
||||
htmlEditorApi?.insertImageLink(image.link!);
|
||||
} else {
|
||||
if (fromFileShare) {
|
||||
await htmlEditorApi?.moveCursorAtLastNode();
|
||||
}
|
||||
await htmlEditorApi?.insertHtml(image.base64Uri ?? '');
|
||||
if (inlineImage.base64Uri?.isNotEmpty == true) {
|
||||
await htmlEditorApi?.insertHtml(inlineImage.base64Uri!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/font_name_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/formatting_options_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/order_list_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/paragraph_type.dart';
|
||||
@@ -194,13 +193,8 @@ class RichTextWebController extends BaseRichTextController {
|
||||
bool isTextStyleTypeSelected(RichTextStyleType richTextStyleType) =>
|
||||
listTextStyleApply.contains(richTextStyleType);
|
||||
|
||||
void insertImage(InlineImage image) async {
|
||||
log('RichTextWebController::insertImage(): $image');
|
||||
if (image.source == ImageSource.network) {
|
||||
editorController.insertNetworkImage(image.link!);
|
||||
} else {
|
||||
editorController.insertHtml("<div>${image.base64Uri ?? ''}</div><br>");
|
||||
}
|
||||
void insertImage(InlineImage inlineImage) {
|
||||
editorController.insertHtml("<div>${inlineImage.base64Uri ?? ''}</div><br>");
|
||||
}
|
||||
|
||||
void applyNewFontStyle(FontNameType? newFont) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
extension FileExtension on File {
|
||||
FileInfo toFileInfo({
|
||||
bool? isInline,
|
||||
bool? isShared
|
||||
}) {
|
||||
return FileInfo(
|
||||
fileName: path.split('/').last,
|
||||
fileSize: existsSync() ? lengthSync() : 0,
|
||||
filePath: path,
|
||||
isInline: isInline,
|
||||
isShared: isShared
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@ extension FileUploadExtension on FileUpload {
|
||||
return FileInfo.fromBytes(
|
||||
bytes: bytes,
|
||||
name: name,
|
||||
size: size
|
||||
size: size,
|
||||
type: type,
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/shared_media_file_extension.dart';
|
||||
|
||||
extension ListSharedMediaFileExtension on List<SharedMediaFile> {
|
||||
List<FileInfo> toListFileInfo({bool? isShared}) => map((sharedMediaFile) => sharedMediaFile.toFileInfo(isShared: isShared)).toList();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/file_extension.dart';
|
||||
|
||||
extension SharedMediaFileExtension on SharedMediaFile {
|
||||
File toFile() {
|
||||
if (PlatformInfo.isIOS) {
|
||||
final pathFile = type == SharedMediaType.FILE
|
||||
? path.toString().replaceAll('file:/', '').replaceAll('%20', ' ')
|
||||
: path.toString().replaceAll('%20', ' ');
|
||||
return File(pathFile);
|
||||
} else {
|
||||
return File(path);
|
||||
}
|
||||
}
|
||||
|
||||
FileInfo toFileInfo({bool? isShared}) => toFile().toFileInfo(isInline: type == SharedMediaType.IMAGE, isShared: isShared);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'dart:async' as async;
|
||||
import 'package:async/async.dart';
|
||||
import 'package:core/domain/extensions/media_type_extension.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
mixin DragDropFileMixin {
|
||||
async.Future<Result<T>> showFutureLoadingDialogFullScreen<T>({
|
||||
required BuildContext context,
|
||||
required async.Future<T> Function() future,
|
||||
}) async {
|
||||
return await showFutureLoadingDialog(
|
||||
context: context,
|
||||
title: AppLocalizations.of(context).loadingPleaseWait,
|
||||
backLabel: AppLocalizations.of(context).close,
|
||||
future: future,
|
||||
);
|
||||
}
|
||||
|
||||
async.Future<List<FileInfo>> onDragDone({
|
||||
required BuildContext context,
|
||||
required DropDoneDetails details
|
||||
}) async {
|
||||
final bytesList = await showFutureLoadingDialogFullScreen(
|
||||
context: context,
|
||||
future: () => async.Future.wait(
|
||||
details.files.map(
|
||||
(xFile) => xFile.readAsBytes(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (bytesList.error != null) return [];
|
||||
|
||||
final listFileInfo = <FileInfo>[];
|
||||
for (var i = 0; i < bytesList.result!.length; i++) {
|
||||
listFileInfo.add(
|
||||
FileInfo(
|
||||
bytes: bytesList.result![i],
|
||||
fileName: details.files[i].name,
|
||||
type: details.files[i].mimeType,
|
||||
fileSize: bytesList.result![i].length,
|
||||
isInline: details.files[i].mimeType?.startsWith(MediaTypeExtension.imageType) == true
|
||||
),
|
||||
);
|
||||
}
|
||||
return listFileInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
enum ImageSource {
|
||||
local,
|
||||
network
|
||||
}
|
||||
@@ -1,25 +1,19 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
class InlineImage with EquatableMixin {
|
||||
final ImageSource source;
|
||||
final String? link;
|
||||
final FileInfo? fileInfo;
|
||||
final String? cid;
|
||||
final FileInfo fileInfo;
|
||||
final String? base64Uri;
|
||||
|
||||
InlineImage(
|
||||
this.source,
|
||||
{
|
||||
this.link,
|
||||
this.fileInfo,
|
||||
this.cid,
|
||||
this.base64Uri,
|
||||
}
|
||||
);
|
||||
InlineImage({
|
||||
required this.fileInfo,
|
||||
this.base64Uri,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [source, link, fileInfo, cid, base64Uri];
|
||||
List<Object?> get props => [
|
||||
fileInfo,
|
||||
base64Uri
|
||||
];
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class BottomBarComposerWidgetStyle {
|
||||
static const double richTextIconSize = 24;
|
||||
static const double sendButtonRadius = 8;
|
||||
static const double sendButtonIconSpace = 5;
|
||||
static const double height = 60;
|
||||
|
||||
static const Color backgroundColor = Colors.white;
|
||||
static const Color iconColor = AppColor.colorRichButtonComposer;
|
||||
@@ -17,7 +18,7 @@ class BottomBarComposerWidgetStyle {
|
||||
static const Color selectedBackgroundColor = AppColor.colorSelected;
|
||||
static const Color selectedIconColor = AppColor.primaryColor;
|
||||
|
||||
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 32, vertical: 12);
|
||||
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 32);
|
||||
static const EdgeInsetsGeometry iconPadding = EdgeInsetsDirectional.all(5);
|
||||
static const EdgeInsetsGeometry sendButtonPadding = EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 24);
|
||||
static const EdgeInsetsGeometry richTextIconPadding = EdgeInsetsDirectional.all(2);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart';
|
||||
|
||||
class DropZoneWidgetStyle {
|
||||
static const double space = 20;
|
||||
@@ -8,11 +9,16 @@ class DropZoneWidgetStyle {
|
||||
|
||||
static const List<double> dashSize = [6, 3];
|
||||
|
||||
static const Color backgroundColor = AppColor.colorDropZoneBackground;
|
||||
static Color backgroundColor = AppColor.colorDropZoneBackground.withOpacity(0.7);
|
||||
static const Color borderColor = AppColor.colorDropZoneBorder;
|
||||
|
||||
static const EdgeInsetsGeometry padding = EdgeInsets.all(20);
|
||||
static const EdgeInsetsGeometry margin = EdgeInsetsDirectional.symmetric(vertical: 8);
|
||||
static const EdgeInsetsGeometry margin = EdgeInsetsDirectional.only(
|
||||
bottom: BottomBarComposerWidgetStyle.height,
|
||||
start: 8,
|
||||
end: 8,
|
||||
top: 8
|
||||
);
|
||||
|
||||
static const TextStyle labelTextStyle = TextStyle(
|
||||
color: Colors.black,
|
||||
|
||||
@@ -26,11 +26,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
final VoidCallback? onUnFocus;
|
||||
final OnMouseDownEditorAction? onMouseDown;
|
||||
final OnEditorSettingsChange? onEditorSettings;
|
||||
final OnImageUploadSuccessAction? onImageUploadSuccessAction;
|
||||
final OnImageUploadFailureAction? onImageUploadFailureAction;
|
||||
final OnEditorTextSizeChanged? onEditorTextSizeChanged;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final VoidCallback? onDragEnter;
|
||||
|
||||
const WebEditorView({
|
||||
super.key,
|
||||
@@ -44,11 +43,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
this.onUnFocus,
|
||||
this.onMouseDown,
|
||||
this.onEditorSettings,
|
||||
this.onImageUploadSuccessAction,
|
||||
this.onImageUploadFailureAction,
|
||||
this.onEditorTextSizeChanged,
|
||||
this.width,
|
||||
this.height,
|
||||
this.onDragEnter,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -71,11 +69,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onUnFocus: onUnFocus,
|
||||
onMouseDown: onMouseDown,
|
||||
onEditorSettings: onEditorSettings,
|
||||
onImageUploadSuccessAction: onImageUploadSuccessAction,
|
||||
onImageUploadFailureAction: onImageUploadFailureAction,
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
);
|
||||
case EmailActionType.editDraft:
|
||||
case EmailActionType.editSendingEmail:
|
||||
@@ -97,11 +94,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onUnFocus: onUnFocus,
|
||||
onMouseDown: onMouseDown,
|
||||
onEditorSettings: onEditorSettings,
|
||||
onImageUploadSuccessAction: onImageUploadSuccessAction,
|
||||
onImageUploadFailureAction: onImageUploadFailureAction,
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
),
|
||||
(success) {
|
||||
if (success is GetEmailContentLoading) {
|
||||
@@ -123,11 +119,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onUnFocus: onUnFocus,
|
||||
onMouseDown: onMouseDown,
|
||||
onEditorSettings: onEditorSettings,
|
||||
onImageUploadSuccessAction: onImageUploadSuccessAction,
|
||||
onImageUploadFailureAction: onImageUploadFailureAction,
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -156,11 +151,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onUnFocus: onUnFocus,
|
||||
onMouseDown: onMouseDown,
|
||||
onEditorSettings: onEditorSettings,
|
||||
onImageUploadSuccessAction: onImageUploadSuccessAction,
|
||||
onImageUploadFailureAction: onImageUploadFailureAction,
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
@@ -185,11 +179,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onUnFocus: onUnFocus,
|
||||
onMouseDown: onMouseDown,
|
||||
onEditorSettings: onEditorSettings,
|
||||
onImageUploadSuccessAction: onImageUploadSuccessAction,
|
||||
onImageUploadFailureAction: onImageUploadFailureAction,
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -205,11 +198,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onUnFocus: onUnFocus,
|
||||
onMouseDown: onMouseDown,
|
||||
onEditorSettings: onEditorSettings,
|
||||
onImageUploadSuccessAction: onImageUploadSuccessAction,
|
||||
onImageUploadFailureAction: onImageUploadFailureAction,
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class AttachmentHeaderComposerWidget extends StatelessWidget {
|
||||
margin: const EdgeInsetsDirectional.only(start: 4),
|
||||
padding: const EdgeInsets.all(3),
|
||||
iconColor: AppColor.colorBackgroundQuotasWarning,
|
||||
tooltipMessage: AppLocalizations.of(context).messageWarningDialogWhenExceedMaximumFileSizeComposer,
|
||||
tooltipMessage: AppLocalizations.of(context).warningMessageWhenExceedGenerallySizeInComposer,
|
||||
backgroundColor: Colors.transparent,
|
||||
)
|
||||
],
|
||||
@@ -89,5 +89,5 @@ class AttachmentHeaderComposerWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
bool get _isExceedMaximumSizeFileAttachedInComposer =>
|
||||
listFileUploaded.totalSize > AppConfig.maximumMegabytesSizeFileAttachedInComposer * 1024 * 1024;
|
||||
listFileUploaded.totalSize > AppConfig.warningAttachmentFileSizeInMegabytes * 1024 * 1024;
|
||||
}
|
||||
|
||||
+18
-39
@@ -3,42 +3,35 @@ import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/styles/web/drop_zone_widget_style.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
typedef OnAddAttachmentFromDropZone = Function(Attachment attachment);
|
||||
typedef OnAttachmentDropZoneListener = Function(Attachment attachment);
|
||||
|
||||
class DropZoneWidget extends StatefulWidget {
|
||||
class AttachmentDropZoneWidget extends StatelessWidget {
|
||||
|
||||
final ImagePaths imagePaths;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final OnAddAttachmentFromDropZone? addAttachmentFromDropZone;
|
||||
final OnAttachmentDropZoneListener? onAttachmentDropZoneListener;
|
||||
|
||||
const DropZoneWidget({
|
||||
const AttachmentDropZoneWidget({
|
||||
super.key,
|
||||
required this.imagePaths,
|
||||
this.width,
|
||||
this.height,
|
||||
this.addAttachmentFromDropZone
|
||||
this.onAttachmentDropZoneListener
|
||||
});
|
||||
|
||||
@override
|
||||
State<DropZoneWidget> createState() => _DropZoneWidgetState();
|
||||
}
|
||||
|
||||
class _DropZoneWidgetState extends State<DropZoneWidget> {
|
||||
|
||||
final _imagePaths = Get.find<ImagePaths>();
|
||||
|
||||
bool _isDragging = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DragTarget<Attachment>(
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
if (_isDragging) {
|
||||
return Padding(
|
||||
builder: (context, _, __) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding: DropZoneWidgetStyle.margin,
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
@@ -48,20 +41,18 @@ class _DropZoneWidgetState extends State<DropZoneWidget> {
|
||||
dashPattern: DropZoneWidgetStyle.dashSize,
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const ShapeDecoration(
|
||||
decoration: ShapeDecoration(
|
||||
color: DropZoneWidgetStyle.backgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(DropZoneWidgetStyle.radius)),
|
||||
),
|
||||
),
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
padding: DropZoneWidgetStyle.padding,
|
||||
alignment: AlignmentDirectional.center,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(_imagePaths.icDropZoneIcon),
|
||||
SvgPicture.asset(imagePaths.icDropZoneIcon),
|
||||
const SizedBox(height: DropZoneWidgetStyle.space),
|
||||
Text(
|
||||
AppLocalizations.of(context).dropFileHereToAttachThem,
|
||||
@@ -71,22 +62,10 @@ class _DropZoneWidgetState extends State<DropZoneWidget> {
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(width: widget.width, height: widget.height);
|
||||
}
|
||||
},
|
||||
onAccept: widget.addAttachmentFromDropZone,
|
||||
onLeave: (attachment) {
|
||||
if (_isDragging) {
|
||||
setState(() => _isDragging = false);
|
||||
}
|
||||
},
|
||||
onMove: (details) {
|
||||
if (!_isDragging) {
|
||||
setState(() => _isDragging = true);
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
onAccept: onAttachmentDropZoneListener
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: BottomBarComposerWidgetStyle.padding,
|
||||
height: BottomBarComposerWidgetStyle.height,
|
||||
color: BottomBarComposerWidgetStyle.backgroundColor,
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/styles/web/drop_zone_widget_style.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
|
||||
typedef OnLocalFileDropZoneListener = Function(DropDoneDetails details);
|
||||
|
||||
class LocalFileDropZoneWidget extends StatelessWidget {
|
||||
|
||||
final ImagePaths imagePaths;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final OnLocalFileDropZoneListener? onLocalFileDropZoneListener;
|
||||
|
||||
const LocalFileDropZoneWidget({
|
||||
super.key,
|
||||
required this.imagePaths,
|
||||
this.width,
|
||||
this.height,
|
||||
this.onLocalFileDropZoneListener
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropTarget(
|
||||
onDragDone: onLocalFileDropZoneListener,
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding: DropZoneWidgetStyle.margin,
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(DropZoneWidgetStyle.radius),
|
||||
color: DropZoneWidgetStyle.borderColor,
|
||||
strokeWidth: DropZoneWidgetStyle.borderWidth,
|
||||
dashPattern: DropZoneWidgetStyle.dashSize,
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
color: DropZoneWidgetStyle.backgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(DropZoneWidgetStyle.radius)),
|
||||
),
|
||||
),
|
||||
padding: DropZoneWidgetStyle.padding,
|
||||
alignment: AlignmentDirectional.center,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(imagePaths.icDropZoneIcon),
|
||||
const SizedBox(height: DropZoneWidgetStyle.space),
|
||||
Text(
|
||||
AppLocalizations.of(context).dropFileHereToAttachThem,
|
||||
style: DropZoneWidgetStyle.labelTextStyle,
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ typedef OnChangeContentEditorAction = Function(String? text);
|
||||
typedef OnInitialContentEditorAction = Function(String text);
|
||||
typedef OnMouseDownEditorAction = Function(BuildContext context);
|
||||
typedef OnEditorSettingsChange = Function(EditorSettings settings);
|
||||
typedef OnImageUploadSuccessAction = Function(FileUpload fileUpload);
|
||||
typedef OnImageUploadFailureAction = Function(FileUpload? fileUpload, String? base64Str, UploadError error);
|
||||
typedef OnEditorTextSizeChanged = Function(int? size);
|
||||
|
||||
class WebEditorWidget extends StatefulWidget {
|
||||
@@ -26,11 +24,10 @@ class WebEditorWidget extends StatefulWidget {
|
||||
final VoidCallback? onUnFocus;
|
||||
final OnMouseDownEditorAction? onMouseDown;
|
||||
final OnEditorSettingsChange? onEditorSettings;
|
||||
final OnImageUploadSuccessAction? onImageUploadSuccessAction;
|
||||
final OnImageUploadFailureAction? onImageUploadFailureAction;
|
||||
final OnEditorTextSizeChanged? onEditorTextSizeChanged;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final VoidCallback? onDragEnter;
|
||||
|
||||
const WebEditorWidget({
|
||||
super.key,
|
||||
@@ -43,11 +40,10 @@ class WebEditorWidget extends StatefulWidget {
|
||||
this.onUnFocus,
|
||||
this.onMouseDown,
|
||||
this.onEditorSettings,
|
||||
this.onImageUploadSuccessAction,
|
||||
this.onImageUploadFailureAction,
|
||||
this.onEditorTextSizeChanged,
|
||||
this.width,
|
||||
this.height,
|
||||
this.onDragEnter,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -133,6 +129,7 @@ class _WebEditorState extends State<WebEditorWidget> {
|
||||
initialText: widget.content,
|
||||
customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(direction: widget.direction),
|
||||
spellCheck: true,
|
||||
disableDragAndDrop: true,
|
||||
webInitialScripts: UnmodifiableListView([
|
||||
WebScript(
|
||||
name: HtmlUtils.lineHeight100Percent.name,
|
||||
@@ -145,7 +142,7 @@ class _WebEditorState extends State<WebEditorWidget> {
|
||||
WebScript(
|
||||
name: HtmlUtils.unregisterDropListener.name,
|
||||
script: HtmlUtils.unregisterDropListener.script,
|
||||
),
|
||||
)
|
||||
])
|
||||
),
|
||||
htmlToolbarOptions: const HtmlToolbarOptions(
|
||||
@@ -154,8 +151,8 @@ class _WebEditorState extends State<WebEditorWidget> {
|
||||
),
|
||||
otherOptions: OtherOptions(
|
||||
height: height,
|
||||
dropZoneWidth: dropZoneWidth,
|
||||
dropZoneHeight: dropZoneHeight,
|
||||
// dropZoneWidth: dropZoneWidth,
|
||||
// dropZoneHeight: dropZoneHeight,
|
||||
),
|
||||
callbacks: Callbacks(
|
||||
onBeforeCommand: widget.onChangeContent,
|
||||
@@ -173,12 +170,12 @@ class _WebEditorState extends State<WebEditorWidget> {
|
||||
onMouseDown: () => widget.onMouseDown?.call(context),
|
||||
onChangeSelection: widget.onEditorSettings,
|
||||
onChangeCodeview: widget.onChangeContent,
|
||||
onImageUpload: widget.onImageUploadSuccessAction,
|
||||
onImageUploadError: widget.onImageUploadFailureAction,
|
||||
onTextFontSizeChanged: widget.onEditorTextSizeChanged,
|
||||
onPaste: () => _editorController.evaluateJavascriptWeb(
|
||||
HtmlUtils.lineHeight100Percent.name
|
||||
),
|
||||
onDragEnter: widget.onDragEnter,
|
||||
onDragLeave: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -339,9 +339,9 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
attachments: controller.attachments,
|
||||
imagePaths: controller.imagePaths,
|
||||
onDragStarted: controller.mailboxDashBoardController.enableDraggableApp,
|
||||
onDragStarted: controller.mailboxDashBoardController.enableAttachmentDraggableApp,
|
||||
onDragEnd: (details) {
|
||||
controller.mailboxDashBoardController.disableDraggableApp();
|
||||
controller.mailboxDashBoardController.disableAttachmentDraggableApp();
|
||||
},
|
||||
downloadAttachmentAction: (attachment) => controller.handleDownloadAttachmentAction(context, attachment),
|
||||
viewAttachmentAction: (attachment) => controller.handleViewAttachmentAction(context, attachment),
|
||||
@@ -376,7 +376,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
Obx(() => CalendarEventDetailWidget(
|
||||
calendarEvent: calendarEvent,
|
||||
emailContent: controller.currentEmailLoaded?.htmlContent ?? '',
|
||||
isDraggableAppActive: controller.mailboxDashBoardController.isDraggableAppActive,
|
||||
isDraggableAppActive: controller.mailboxDashBoardController.isAttachmentDraggableAppActive,
|
||||
onOpenComposerAction: controller.openNewComposerAction,
|
||||
onOpenNewTabAction: controller.openNewTabAction,
|
||||
onMailtoDelegateAction: controller.openMailToLink,
|
||||
@@ -407,7 +407,14 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
mailtoDelegate: controller.openMailToLink,
|
||||
direction: AppUtils.getCurrentDirection(context),
|
||||
),
|
||||
if (controller.mailboxDashBoardController.isDraggableAppActive)
|
||||
if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive)
|
||||
PointerInterceptor(
|
||||
child: SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
)
|
||||
),
|
||||
if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive)
|
||||
PointerInterceptor(
|
||||
child: SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
|
||||
@@ -1,38 +1,7 @@
|
||||
import 'package:core/data/constants/constant.dart';
|
||||
import 'package:core/domain/extensions/media_type_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart';
|
||||
|
||||
extension AttachmentExtension on Attachment {
|
||||
|
||||
String getIcon(ImagePaths imagePaths, {MediaType? fileMediaType}) {
|
||||
final mediaType = type ?? fileMediaType;
|
||||
|
||||
if (isDisplayedPDFIcon) {
|
||||
return imagePaths.icFilePdf;
|
||||
}
|
||||
|
||||
if (mediaType == null) {
|
||||
return imagePaths.icFileEPup;
|
||||
}
|
||||
if (mediaType.isDocFile()) {
|
||||
return imagePaths.icFileDocx;
|
||||
} else if (mediaType.isExcelFile()) {
|
||||
return imagePaths.icFileXlsx;
|
||||
} else if (mediaType.isPowerPointFile()) {
|
||||
return imagePaths.icFilePptx;
|
||||
} else if (mediaType.isPdfFile()) {
|
||||
return imagePaths.icFilePdf;
|
||||
} else if (mediaType.isZipFile()) {
|
||||
return imagePaths.icFileZip;
|
||||
} else if (mediaType.isImageFile()) {
|
||||
return imagePaths.icFilePng;
|
||||
}
|
||||
return imagePaths.icFileEPup;
|
||||
}
|
||||
|
||||
bool get isDisplayedPDFIcon => type?.mimeType == Constant.pdfMimeType
|
||||
|| (type?.mimeType == Constant.octetStreamMimeType
|
||||
&& name?.endsWith(Constant.pdfExtension) == true);
|
||||
String getIcon(ImagePaths imagePaths) => type?.getIcon(imagePaths, fileName: name) ?? imagePaths.icFileEPup;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import 'dart:io';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:get/get_connect/http/src/request/request.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||
import 'package:model/account/authentication_type.dart';
|
||||
import 'package:model/account/password.dart';
|
||||
@@ -122,8 +121,6 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
|
||||
final uploadExtra = extraInRequest[FileUploader.uploadAttachmentExtraKey];
|
||||
|
||||
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token);
|
||||
requestOptions.headers[HttpHeaders.contentTypeHeader] = uploadExtra[FileUploader.typeExtraKey];
|
||||
requestOptions.headers[HttpHeaders.contentLengthHeader] = uploadExtra[FileUploader.sizeExtraKey];
|
||||
|
||||
final newOptions = Options(
|
||||
method: requestOptions.method,
|
||||
@@ -155,11 +152,16 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
|
||||
}
|
||||
|
||||
Stream<List<int>>? _getDataUploadRequest(dynamic mapUploadExtra) {
|
||||
final currentPlatform = mapUploadExtra[FileUploader.platformExtraKey];
|
||||
if (currentPlatform == 'web') {
|
||||
return BodyBytesStream.fromBytes(mapUploadExtra[FileUploader.bytesExtraKey]);
|
||||
} else {
|
||||
return File(mapUploadExtra[FileUploader.filePathExtraKey]).openRead();
|
||||
try {
|
||||
String? filePath = mapUploadExtra[FileUploader.filePathExtraKey];
|
||||
if (filePath?.isNotEmpty == true) {
|
||||
return File(filePath!).openRead();
|
||||
} else {
|
||||
return mapUploadExtra[FileUploader.streamDataExtraKey];
|
||||
}
|
||||
} catch(e) {
|
||||
log('AuthorizationInterceptors::_getDataUploadRequest: Exception = $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -216,8 +216,9 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
final searchMailboxActivated = RxBool(false);
|
||||
final listSendingEmails = RxList<SendingEmail>();
|
||||
final refreshingMailboxState = Rx<Either<Failure, Success>>(Right(UIState.idle));
|
||||
final draggableAppState = Rxn<DraggableAppState>();
|
||||
final attachmentDraggableAppState = Rxn<DraggableAppState>();
|
||||
final isRecoveringDeletedMessage = RxBool(false);
|
||||
final localFileDraggableAppState = Rxn<DraggableAppState>();
|
||||
|
||||
Session? sessionCurrent;
|
||||
Map<Role, MailboxId> mapDefaultMailboxIdByRole = {};
|
||||
@@ -2148,14 +2149,16 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
openMailboxAction(inboxPresentation);
|
||||
}
|
||||
|
||||
bool get isDraggableAppActive => draggableAppState.value == DraggableAppState.active;
|
||||
bool get isAttachmentDraggableAppActive => attachmentDraggableAppState.value == DraggableAppState.active;
|
||||
|
||||
void enableDraggableApp() {
|
||||
draggableAppState.value = DraggableAppState.active;
|
||||
bool get isLocalFileDraggableAppActive => localFileDraggableAppState.value == DraggableAppState.active;
|
||||
|
||||
void enableAttachmentDraggableApp() {
|
||||
attachmentDraggableAppState.value = DraggableAppState.active;
|
||||
}
|
||||
|
||||
void disableDraggableApp() {
|
||||
draggableAppState.value = DraggableAppState.inActive;
|
||||
void disableAttachmentDraggableApp() {
|
||||
attachmentDraggableAppState.value = DraggableAppState.inActive;
|
||||
}
|
||||
|
||||
void saveEmailToDraft({required SaveToDraftArguments arguments}) {
|
||||
|
||||
@@ -27,10 +27,7 @@ import 'package:worker_manager/worker_manager.dart' as worker;
|
||||
class FileUploader {
|
||||
|
||||
static const String uploadAttachmentExtraKey = 'upload-attachment';
|
||||
static const String platformExtraKey = 'platform';
|
||||
static const String bytesExtraKey = 'bytes';
|
||||
static const String typeExtraKey = 'type';
|
||||
static const String sizeExtraKey = 'size';
|
||||
static const String streamDataExtraKey = 'streamData';
|
||||
static const String filePathExtraKey = 'path';
|
||||
|
||||
final DioClient _dioClient;
|
||||
@@ -94,10 +91,7 @@ class FileUploader {
|
||||
|
||||
final mapExtra = <String, dynamic>{
|
||||
uploadAttachmentExtraKey: {
|
||||
platformExtraKey: 'mobile',
|
||||
filePathExtraKey: argsUpload.mobileFileUpload.filePath,
|
||||
typeExtraKey: argsUpload.mobileFileUpload.mimeType,
|
||||
sizeExtraKey: argsUpload.mobileFileUpload.fileSize,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,7 +103,7 @@ class FileUploader {
|
||||
),
|
||||
data: File(argsUpload.mobileFileUpload.filePath).openRead(),
|
||||
onSendProgress: (count, total) {
|
||||
log('FileUploader::_handleUploadAttachmentAction():onSendProgress: [${argsUpload.uploadId.id}] = $count');
|
||||
log('FileUploader::_handleUploadAttachmentAction():onSendProgress: FILE[${argsUpload.uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
|
||||
sendPort.send(
|
||||
UploadingAttachmentUploadState(
|
||||
argsUpload.uploadId,
|
||||
@@ -139,10 +133,7 @@ class FileUploader {
|
||||
|
||||
final mapExtra = <String, dynamic>{
|
||||
uploadAttachmentExtraKey: {
|
||||
platformExtraKey: 'web',
|
||||
bytesExtraKey: fileInfo.bytes,
|
||||
typeExtraKey: fileInfo.mimeType,
|
||||
sizeExtraKey: fileInfo.fileSize,
|
||||
streamDataExtraKey: BodyBytesStream.fromBytes(fileInfo.bytes!),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +146,7 @@ class FileUploader {
|
||||
data: BodyBytesStream.fromBytes(fileInfo.bytes!),
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: (count, total) {
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [${uploadId.id}] = $count');
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
|
||||
onSendController.add(
|
||||
Right(UploadingAttachmentUploadState(
|
||||
uploadId,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
class PickFileCanceledException implements Exception {}
|
||||
@@ -3,5 +3,15 @@ import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/mobile_file_upload.dart';
|
||||
|
||||
extension FileInfoExtension on FileInfo {
|
||||
MobileFileUpload toMobileFileUpload() => MobileFileUpload(fileName, filePath, fileSize, mimeType);
|
||||
MobileFileUpload toMobileFileUpload() => MobileFileUpload(fileName, filePath!, fileSize, mimeType);
|
||||
|
||||
FileInfo withInline() => FileInfo(
|
||||
fileName: fileName,
|
||||
fileSize: fileSize,
|
||||
filePath: filePath,
|
||||
bytes: bytes,
|
||||
readStream: readStream,
|
||||
type: type,
|
||||
isInline: true,
|
||||
isShared: isShared);
|
||||
}
|
||||
@@ -3,5 +3,5 @@ import 'package:model/upload/file_info.dart';
|
||||
extension ListFileInfoExtension on List<FileInfo> {
|
||||
List<int> get listSize => map((file) => file.fileSize).toList();
|
||||
|
||||
num get totalSize => listSize.reduce((sum, size) => sum + size);
|
||||
num get totalSize => listSize.isEmpty ? 0 : listSize.reduce((sum, size) => sum + size);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:core/domain/extensions/media_type_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
extension MediaTypeExtension on MediaType {
|
||||
String getIcon(ImagePaths imagePaths, {String? fileName}) {
|
||||
if (validatePDFIcon(fileName: fileName) == true) {
|
||||
return imagePaths.icFilePdf;
|
||||
} else if (isDocFile()) {
|
||||
return imagePaths.icFileDocx;
|
||||
} else if (isExcelFile()) {
|
||||
return imagePaths.icFileXlsx;
|
||||
} else if (isPowerPointFile()) {
|
||||
return imagePaths.icFilePptx;
|
||||
} else if (isPdfFile()) {
|
||||
return imagePaths.icFilePdf;
|
||||
} else if (isZipFile()) {
|
||||
return imagePaths.icFileZip;
|
||||
} else if (isImageFile()) {
|
||||
return imagePaths.icFilePng;
|
||||
} else {
|
||||
return imagePaths.icFileEPup;
|
||||
}
|
||||
}
|
||||
|
||||
bool validatePDFIcon({required String? fileName}) => mimeType == 'application/pdf' ||
|
||||
(mimeType == 'application/octet-stream' && fileName?.endsWith('.pdf') == true);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
extension PlatformFileExtension on PlatformFile {
|
||||
FileInfo toFileInfo() => FileInfo(
|
||||
fileName: name,
|
||||
fileSize: size,
|
||||
filePath: PlatformInfo.isWeb ? '' : path ?? '',
|
||||
bytes: bytes,
|
||||
readStream: readStream
|
||||
);
|
||||
}
|
||||
@@ -31,15 +31,11 @@ class SuccessAttachmentUploadState extends Success {
|
||||
final UploadTaskId uploadId;
|
||||
final Attachment attachment;
|
||||
final FileInfo fileInfo;
|
||||
final bool fromFileShared;
|
||||
|
||||
SuccessAttachmentUploadState(
|
||||
this.uploadId,
|
||||
this.attachment,
|
||||
this.fileInfo,
|
||||
{
|
||||
this.fromFileShared = false
|
||||
}
|
||||
this.fileInfo
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -47,7 +43,6 @@ class SuccessAttachmentUploadState extends Success {
|
||||
uploadId,
|
||||
attachment,
|
||||
fileInfo,
|
||||
fromFileShared,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
class LocalFilePickerLoading extends LoadingState {}
|
||||
|
||||
class LocalFilePickerSuccess extends UIState {
|
||||
final List<FileInfo> pickedFiles;
|
||||
|
||||
@@ -14,6 +16,4 @@ class LocalFilePickerSuccess extends UIState {
|
||||
class LocalFilePickerFailure extends FeatureFailure {
|
||||
|
||||
LocalFilePickerFailure(dynamic exception) : super(exception: exception);
|
||||
}
|
||||
|
||||
class LocalFilePickerCancel extends FeatureFailure {}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
class LocalImagePickerLoading extends LoadingState {}
|
||||
|
||||
class LocalImagePickerSuccess extends UIState {
|
||||
final FileInfo fileInfo;
|
||||
|
||||
LocalImagePickerSuccess(this.fileInfo);
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileInfo];
|
||||
}
|
||||
|
||||
class LocalImagePickerFailure extends FeatureFailure {
|
||||
|
||||
LocalImagePickerFailure(dynamic exception) : super(exception: exception);
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/platform_file_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
|
||||
|
||||
class LocalFilePickerInteractor {
|
||||
@@ -13,23 +14,22 @@ class LocalFilePickerInteractor {
|
||||
|
||||
Stream<Either<Failure, Success>> execute({FileType fileType = FileType.any}) async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(LocalFilePickerLoading());
|
||||
|
||||
final filesResult = await FilePicker.platform.pickFiles(
|
||||
type: fileType,
|
||||
allowMultiple: true,
|
||||
withData: PlatformInfo.isWeb
|
||||
withData: PlatformInfo.isWeb,
|
||||
withReadStream: PlatformInfo.isMobile
|
||||
);
|
||||
if (filesResult != null && filesResult.files.isNotEmpty) {
|
||||
final fileInfoResults = filesResult.files
|
||||
.map((platformFile) => FileInfo(
|
||||
platformFile.name,
|
||||
PlatformInfo.isWeb ? '' : platformFile.path ?? '',
|
||||
platformFile.size,
|
||||
bytes: PlatformInfo.isWeb ? platformFile.bytes : null
|
||||
))
|
||||
.toList();
|
||||
yield Right<Failure, Success>(LocalFilePickerSuccess(fileInfoResults));
|
||||
|
||||
if (filesResult?.files.isNotEmpty == true) {
|
||||
final listFileInfo = filesResult!.files
|
||||
.map((platformFile) => platformFile.toFileInfo())
|
||||
.toList();
|
||||
yield Right<Failure, Success>(LocalFilePickerSuccess(listFileInfo));
|
||||
} else {
|
||||
yield Left<Failure, Success>(LocalFilePickerCancel());
|
||||
yield Left<Failure, Success>(LocalFilePickerFailure(PickFileCanceledException()));
|
||||
}
|
||||
} catch (exception) {
|
||||
yield Left<Failure, Success>(LocalFilePickerFailure(exception));
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/platform_file_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_image_picker_state.dart';
|
||||
|
||||
class LocalImagePickerInteractor {
|
||||
|
||||
LocalImagePickerInteractor();
|
||||
|
||||
Stream<Either<Failure, Success>> execute() async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(LocalImagePickerLoading());
|
||||
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: false,
|
||||
withReadStream: PlatformInfo.isMobile,
|
||||
withData: PlatformInfo.isWeb
|
||||
);
|
||||
|
||||
if (filePickerResult?.files.isNotEmpty == true) {
|
||||
final fileInfo = filePickerResult!.files.first.toFileInfo();
|
||||
yield Right<Failure, Success>(LocalImagePickerSuccess(fileInfo));
|
||||
} else {
|
||||
yield Left<Failure, Success>(LocalImagePickerFailure(PickFileCanceledException()));
|
||||
}
|
||||
} catch (exception) {
|
||||
yield Left<Failure, Success>(LocalImagePickerFailure(exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,13 @@ import 'package:get/get.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:model/extensions/attachment_extension.dart';
|
||||
import 'package:model/extensions/list_attachment_extension.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||
import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
|
||||
import 'package:tmail_ui_user/features/upload/presentation/extensions/upload_attachment_extension.dart';
|
||||
@@ -80,7 +80,7 @@ class UploadController extends BaseController {
|
||||
failure.uploadId,
|
||||
(currentState) => currentState?.copyWith(uploadStatus: UploadFileStatus.uploadFailed));
|
||||
deleteFileUploaded(failure.uploadId);
|
||||
_handleUploadAttachmentsFailure(failure);
|
||||
_showToastMessageWhenUploadAttachmentsFailure(failure);
|
||||
}
|
||||
},
|
||||
(success) {
|
||||
@@ -115,7 +115,7 @@ class UploadController extends BaseController {
|
||||
);
|
||||
|
||||
_refreshListUploadAttachmentState();
|
||||
_handleUploadAttachmentsSuccess(success);
|
||||
_showToastMessageWhenUploadAttachmentsSuccess(success);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -162,7 +162,7 @@ class UploadController extends BaseController {
|
||||
);
|
||||
|
||||
final uploadFileState = _uploadingStateInlineFiles.getUploadFileStateById(success.uploadId);
|
||||
log('UploadController::_handleProgressUploadInlineImageStateStream:uploadId: ${uploadFileState?.uploadTaskId} | fromFileShared: ${uploadFileState?.fromFileShared}');
|
||||
log('UploadController::_handleProgressUploadInlineImageStateStream:uploadId: ${uploadFileState?.uploadTaskId} | fromFileShared: ${uploadFileState?.file?.isShared}');
|
||||
|
||||
_uploadingStateInlineFiles.updateElementByUploadTaskId(
|
||||
success.uploadId,
|
||||
@@ -179,7 +179,6 @@ class UploadController extends BaseController {
|
||||
success.uploadId,
|
||||
inlineAttachment,
|
||||
success.fileInfo,
|
||||
fromFileShared: uploadFileState?.fromFileShared ?? false
|
||||
);
|
||||
_handleUploadInlineAttachmentsSuccess(newUploadSuccess);
|
||||
}
|
||||
@@ -203,27 +202,24 @@ class UploadController extends BaseController {
|
||||
_refreshListUploadAttachmentState();
|
||||
}
|
||||
|
||||
Future<void> justUploadAttachmentsAction(List<FileInfo> uploadFiles, Uri uploadUri) {
|
||||
Future<void> justUploadAttachmentsAction({
|
||||
required List<FileInfo> uploadFiles,
|
||||
required Uri uploadUri,
|
||||
}) {
|
||||
return Future.forEach<FileInfo>(uploadFiles, (uploadFile) async {
|
||||
await uploadFileAction(uploadFile, uploadUri);
|
||||
await uploadFileAction(uploadFile: uploadFile, uploadUri: uploadUri);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> uploadFileAction(
|
||||
FileInfo uploadFile,
|
||||
Uri uploadUri,
|
||||
{
|
||||
bool isInline = false,
|
||||
bool fromFileShared = false,
|
||||
}
|
||||
) {
|
||||
log('UploadController::_uploadFile():fileName: ${uploadFile.fileName} | isInline: $isInline | fromFileShared: $fromFileShared');
|
||||
Future<void> uploadFileAction({
|
||||
required FileInfo uploadFile,
|
||||
required Uri uploadUri,
|
||||
}) {
|
||||
log('UploadController::_uploadFile():fileName: ${uploadFile.fileName} | mimeType: ${uploadFile.mimeType} | isInline: ${uploadFile.isInline} | fromFileShared: ${uploadFile.isShared}');
|
||||
consumeState(_uploadAttachmentInteractor.execute(
|
||||
uploadFile,
|
||||
uploadUri,
|
||||
cancelToken: CancelToken(),
|
||||
isInline: isInline,
|
||||
fromFileShared: fromFileShared
|
||||
));
|
||||
return Future.value();
|
||||
}
|
||||
@@ -244,6 +240,16 @@ class UploadController extends BaseController {
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<FileInfo> get attachmentsPicked {
|
||||
if (listUploadAttachments.isEmpty) {
|
||||
return List.empty();
|
||||
}
|
||||
return listUploadAttachments
|
||||
.map((fileState) => fileState.file)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
}
|
||||
|
||||
Set<EmailBodyPart>? generateAttachments() {
|
||||
if (attachmentsUploaded.isEmpty) {
|
||||
return null;
|
||||
@@ -254,7 +260,7 @@ class UploadController extends BaseController {
|
||||
.toSet();
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentsFailure(ErrorAttachmentUploadState failure) {
|
||||
void _showToastMessageWhenUploadAttachmentsFailure(ErrorAttachmentUploadState failure) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
@@ -264,7 +270,7 @@ class UploadController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentsSuccess(SuccessAttachmentUploadState success) {
|
||||
void _showToastMessageWhenUploadAttachmentsSuccess(SuccessAttachmentUploadState success) {
|
||||
if (currentContext != null && currentOverlayContext != null && _uploadingStateFiles.allSuccess) {
|
||||
appToast.showToastSuccessMessage(
|
||||
currentOverlayContext!,
|
||||
@@ -275,7 +281,7 @@ class UploadController extends BaseController {
|
||||
}
|
||||
|
||||
bool isExceededMaxSizeAttachmentsPerEmail({num totalSizePreparedFiles = 0}) {
|
||||
final currentTotalSize = attachmentsUploaded.totalSize + inlineAttachmentsUploaded.totalSize + totalSizePreparedFiles;
|
||||
final currentTotalSize = attachmentsPicked.totalSize + inlineAttachmentsPicked.totalSize + totalSizePreparedFiles;
|
||||
final maxSizeAttachmentsPerEmail = _mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value;
|
||||
log('UploadController::isExceededMaxSizeAttachmentsPerEmail(): currentTotalSize = $currentTotalSize | maxSizeAttachmentsPerEmail = $maxSizeAttachmentsPerEmail');
|
||||
if (maxSizeAttachmentsPerEmail != null) {
|
||||
@@ -285,16 +291,17 @@ class UploadController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
bool isExceededMaxSizeFilesAttachedInComposer({num totalSizePreparedFiles = 0}) {
|
||||
final currentTotalSizeAttachments = attachmentsUploaded.totalSize + totalSizePreparedFiles;
|
||||
const maximumBytesSizeFileAttachedInComposer = AppConfig.maximumMegabytesSizeFileAttachedInComposer * 1024 * 1024;
|
||||
bool isExceededWarningAttachmentFileSizeInComposer({num totalSizePreparedFiles = 0}) {
|
||||
final currentTotalSizeAttachments = attachmentsPicked.totalSize + totalSizePreparedFiles;
|
||||
const maximumBytesSizeFileAttachedInComposer = AppConfig.warningAttachmentFileSizeInMegabytes * 1024 * 1024;
|
||||
log('UploadController::isExceededMaxSizeFilesAttachedInComposer(): currentTotalSizeAttachments = $currentTotalSizeAttachments | maximumBytesSizeFileAttachedInComposer = $maximumBytesSizeFileAttachedInComposer');
|
||||
return currentTotalSizeAttachments > maximumBytesSizeFileAttachedInComposer;
|
||||
}
|
||||
|
||||
void validateTotalSizeAttachmentsBeforeUpload({
|
||||
required num totalSizePreparedFiles,
|
||||
VoidCallback? callbackAction
|
||||
num? totalSizePreparedFilesWithDispositionAttachment,
|
||||
VoidCallback? onValidationSuccess
|
||||
}) {
|
||||
log('UploadController::_validateTotalSizeAttachmentsBeforeUpload: totalSizePreparedFiles = $totalSizePreparedFiles');
|
||||
if (isExceededMaxSizeAttachmentsPerEmail(totalSizePreparedFiles: totalSizePreparedFiles)) {
|
||||
@@ -307,7 +314,7 @@ class UploadController extends BaseController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isExceededMaxSizeFilesAttachedInComposer(totalSizePreparedFiles: totalSizePreparedFiles)) {
|
||||
if (isExceededWarningAttachmentFileSizeInComposer(totalSizePreparedFiles: totalSizePreparedFilesWithDispositionAttachment ?? totalSizePreparedFiles)) {
|
||||
if (currentContext == null) {
|
||||
log('UploadController::_validateTotalSizeAttachmentsBeforeUpload: CONTEXT IS NULL');
|
||||
return;
|
||||
@@ -315,12 +322,34 @@ class UploadController extends BaseController {
|
||||
|
||||
_showWarningDialogWhenExceededMaxSizeFilesAttachedInComposer(
|
||||
context: currentContext!,
|
||||
confirmAction: callbackAction
|
||||
confirmAction: () async {
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
onValidationSuccess
|
||||
);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
callbackAction?.call();
|
||||
onValidationSuccess?.call();
|
||||
}
|
||||
|
||||
void validateTotalSizeInlineAttachmentsBeforeUpload({
|
||||
required num totalSizePreparedFiles,
|
||||
VoidCallback? onValidationSuccess
|
||||
}) {
|
||||
if (isExceededMaxSizeAttachmentsPerEmail(totalSizePreparedFiles: totalSizePreparedFiles)) {
|
||||
if (currentContext == null) {
|
||||
log('UploadController::validateTotalSizeInlineAttachmentsBeforeUpload: CONTEXT IS NULL');
|
||||
return;
|
||||
}
|
||||
|
||||
_showConfirmDialogWhenExceededMaxSizeAttachmentsPerEmail(context: currentContext!);
|
||||
return;
|
||||
}
|
||||
|
||||
onValidationSuccess?.call();
|
||||
}
|
||||
|
||||
void _showConfirmDialogWhenExceededMaxSizeAttachmentsPerEmail({required BuildContext context}) {
|
||||
@@ -340,7 +369,7 @@ class UploadController extends BaseController {
|
||||
showConfirmDialogAction(
|
||||
context,
|
||||
title: '',
|
||||
AppLocalizations.of(context).messageWarningDialogWhenExceedMaximumFileSizeComposer,
|
||||
AppLocalizations.of(context).warningMessageWhenExceedGenerallySizeInComposer,
|
||||
AppLocalizations.of(context).continueAction,
|
||||
cancelTitle: AppLocalizations.of(context).cancel,
|
||||
alignCenter: true,
|
||||
@@ -394,6 +423,17 @@ class UploadController extends BaseController {
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<FileInfo> get inlineAttachmentsPicked {
|
||||
if (_uploadingStateInlineFiles.uploadingStateFiles.isEmpty) {
|
||||
return List.empty();
|
||||
}
|
||||
return _uploadingStateInlineFiles.uploadingStateFiles
|
||||
.whereNotNull()
|
||||
.map((fileState) => fileState.file)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
}
|
||||
|
||||
Map<String, Attachment> get mapInlineAttachments {
|
||||
final inlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles
|
||||
.whereNotNull()
|
||||
@@ -415,42 +455,47 @@ class UploadController extends BaseController {
|
||||
return mapInlineAttachments;
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentFailure(UploadAttachmentFailure failure) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
failure.fileInfo.isInline == true
|
||||
? AppLocalizations.of(currentContext!).thisImageCannotBeAdded
|
||||
: AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
|
||||
leadingSVGIconColor: Colors.white,
|
||||
leadingSVGIcon: failure.fileInfo.isInline == true
|
||||
? imagePaths.icInsertImage
|
||||
: imagePaths.icAttachment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentSuccess(UploadAttachmentSuccess success) async {
|
||||
if (success.uploadAttachment.fileInfo.isInline == true) {
|
||||
_uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState());
|
||||
await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
} else {
|
||||
_uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
|
||||
await _progressUploadStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
_refreshListUploadAttachmentState();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleFailureViewState(Failure failure) async {
|
||||
super.handleFailureViewState(failure);
|
||||
void handleFailureViewState(Failure failure) {
|
||||
if (failure is UploadAttachmentFailure) {
|
||||
if (failure.isInline) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).thisImageCannotBeAdded,
|
||||
leadingSVGIconColor: Colors.white,
|
||||
leadingSVGIcon: imagePaths.icInsertImage);
|
||||
}
|
||||
} else {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
|
||||
leadingSVGIconColor: Colors.white,
|
||||
leadingSVGIcon: imagePaths.icAttachment);
|
||||
}
|
||||
}
|
||||
_handleUploadAttachmentFailure(failure);
|
||||
} else {
|
||||
super.handleFailureViewState(failure);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSuccessViewState(Success success) async {
|
||||
super.handleSuccessViewState(success);
|
||||
if (success is UploadAttachmentSuccess) {
|
||||
if (success.isInline) {
|
||||
_uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState(fromFileShared: success.fromFileShared));
|
||||
await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
} else {
|
||||
_uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
|
||||
await _progressUploadStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
_refreshListUploadAttachmentState();
|
||||
}
|
||||
_handleUploadAttachmentSuccess(success);
|
||||
} else {
|
||||
super.handleSuccessViewState(success);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,11 @@ import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_sta
|
||||
|
||||
extension UploadAttachmentExtension on UploadAttachment {
|
||||
|
||||
UploadFileState toUploadFileState({bool fromFileShared = false}) {
|
||||
UploadFileState toUploadFileState() {
|
||||
return UploadFileState(
|
||||
uploadTaskId,
|
||||
file: fileInfo,
|
||||
cancelToken: cancelToken,
|
||||
fromFileShared: fromFileShared,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
|
||||
|
||||
@@ -17,7 +18,6 @@ class UploadFileState with EquatableMixin {
|
||||
final int uploadingProgress;
|
||||
final Attachment? attachment;
|
||||
final CancelToken? cancelToken;
|
||||
final bool fromFileShared;
|
||||
|
||||
UploadFileState(
|
||||
this.uploadTaskId,
|
||||
@@ -27,7 +27,6 @@ class UploadFileState with EquatableMixin {
|
||||
this.uploadingProgress = 0,
|
||||
this.attachment,
|
||||
this.cancelToken,
|
||||
this.fromFileShared = false,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -38,7 +37,6 @@ class UploadFileState with EquatableMixin {
|
||||
int? uploadingProgress,
|
||||
Attachment? attachment,
|
||||
CancelToken? cancelToken,
|
||||
bool? fromFileShared,
|
||||
}) {
|
||||
return UploadFileState(
|
||||
uploadTaskId ?? this.uploadTaskId,
|
||||
@@ -47,7 +45,6 @@ class UploadFileState with EquatableMixin {
|
||||
uploadingProgress: uploadingProgress ?? this.uploadingProgress,
|
||||
attachment: attachment ?? this.attachment,
|
||||
cancelToken: cancelToken ?? this.cancelToken,
|
||||
fromFileShared: fromFileShared ?? this.fromFileShared
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,11 +69,16 @@ class UploadFileState with EquatableMixin {
|
||||
double get percentUploading => uploadingProgress / 100;
|
||||
|
||||
String getIcon(ImagePaths imagePaths) {
|
||||
var mediaType = attachment?.type;
|
||||
if (mediaType == null && file != null) {
|
||||
mediaType = MediaType.parse(file!.mimeType);
|
||||
try {
|
||||
MediaType? mediaType = attachment?.type;
|
||||
if (mediaType == null && file != null) {
|
||||
mediaType = MediaType.parse(file!.mimeType);
|
||||
}
|
||||
return mediaType?.getIcon(imagePaths, fileName: fileName) ?? imagePaths.icFileEPup;
|
||||
} catch (e) {
|
||||
logError('UploadFileState::getIcon: Exception: $e');
|
||||
return imagePaths.icFileEPup;
|
||||
}
|
||||
return attachment?.getIcon(imagePaths, fileMediaType: mediaType) ?? imagePaths.icFileEPup;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -87,6 +89,5 @@ class UploadFileState with EquatableMixin {
|
||||
uploadingProgress,
|
||||
attachment,
|
||||
cancelToken,
|
||||
fromFileShared,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user