TF-2644 Handle total size exceeded maximum size when drag & drop multiple file to composer
This commit is contained in:
@@ -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: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user