TF-2644 Handle total size exceeded maximum size when drag & drop multiple file to composer
This commit is contained in:
@@ -45,6 +45,7 @@ export 'utils/file_utils.dart';
|
||||
export 'utils/option_param_mixin.dart';
|
||||
export 'utils/print_utils.dart';
|
||||
export 'utils/broadcast_channel/broadcast_channel.dart';
|
||||
export 'utils/list_utils.dart';
|
||||
|
||||
// Views
|
||||
export 'presentation/views/text/slogan_builder.dart';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
Tuple2<List<T>, List<T>> partition<T>(List<T> list, bool Function(T) predicate) {
|
||||
List<T> trueList = [];
|
||||
List<T> falseList = [];
|
||||
|
||||
for (var element in list) {
|
||||
if (predicate(element)) {
|
||||
trueList.add(element);
|
||||
} else {
|
||||
falseList.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
return Tuple2(trueList, falseList);
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3239,8 +3239,8 @@
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"messageWarningDialogWhenExceedMaximumFileSizeComposer": "Your message is larger than the size generally accepted by third party email systems. If you confirm sending this mail, there is a risk that it gets rejected by your recipient system.",
|
||||
"@messageWarningDialogWhenExceedMaximumFileSizeComposer": {
|
||||
"warningMessageWhenExceedGenerallySizeInComposer": "Your message is larger than the size generally accepted by third party email systems. If you confirm sending this mail, there is a risk that it gets rejected by your recipient system.",
|
||||
"@warningMessageWhenExceedGenerallySizeInComposer": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
|
||||
@@ -3936,8 +3936,8 @@
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"messageWarningDialogWhenExceedMaximumFileSizeComposer": "Votre message dépasse la taille généralement acceptée pour un email. \nSi vous confirmez l'envoi il y a un risque que le mail soit rejeté par le système de votre interlocuteur.",
|
||||
"@messageWarningDialogWhenExceedMaximumFileSizeComposer": {
|
||||
"warningMessageWhenExceedGenerallySizeInComposer": "Votre message dépasse la taille généralement acceptée pour un email. \nSi vous confirmez l'envoi il y a un risque que le mail soit rejeté par le système de votre interlocuteur.",
|
||||
"@warningMessageWhenExceedGenerallySizeInComposer": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@last_modified": "2024-02-29T16:08:18.479839",
|
||||
"@@last_modified": "2024-03-15T21:34:52.177175",
|
||||
"initializing_data": "Initializing data...",
|
||||
"@initializing_data": {
|
||||
"type": "text",
|
||||
@@ -3750,8 +3750,8 @@
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"messageWarningDialogWhenExceedMaximumFileSizeComposer": "Your message is larger than the size generally accepted by third party email systems. If you confirm sending this mail, there is a risk that it gets rejected by your recipient system.",
|
||||
"@messageWarningDialogWhenExceedMaximumFileSizeComposer": {
|
||||
"warningMessageWhenExceedGenerallySizeInComposer": "Your message is larger than the size generally accepted by third party email systems. If you confirm sending this mail, there is a risk that it gets rejected by your recipient system.",
|
||||
"@warningMessageWhenExceedGenerallySizeInComposer": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
@@ -3761,5 +3761,17 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"thisFileCannotBePicked": "This file cannot be picked.",
|
||||
"@thisFileCannotBePicked": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"loadingPleaseWait": "Loading... Please wait!",
|
||||
"@loadingPleaseWait": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
@@ -3944,6 +3944,8 @@
|
||||
},
|
||||
"messageWarningDialogWhenExceedMaximumFileSizeComposer": "Thư của bạn lớn hơn kích thước thường được hệ thống email của bên thứ ba chấp nhận. Nếu bạn xác nhận đã gửi thư này, có nguy cơ nó sẽ bị hệ thống người nhận từ chối.",
|
||||
"@messageWarningDialogWhenExceedMaximumFileSizeComposer": {
|
||||
"warningMessageWhenExceedGenerallySizeInComposer": "Thư của bạn lớn hơn kích thước thường được hệ thống email của bên thứ ba chấp nhận. Nếu bạn xác nhận đã gửi thư này, có nguy cơ nó sẽ bị hệ thống người nhận từ chối.",
|
||||
"@warningMessageWhenExceedGenerallySizeInComposer": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
|
||||
@@ -3917,10 +3917,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
String get messageWarningDialogWhenExceedMaximumFileSizeComposer {
|
||||
String get warningMessageWhenExceedGenerallySizeInComposer {
|
||||
return Intl.message(
|
||||
'Your message is larger than the size generally accepted by third party email systems. If you confirm sending this mail, there is a risk that it gets rejected by your recipient system.',
|
||||
name: 'messageWarningDialogWhenExceedMaximumFileSizeComposer',
|
||||
name: 'warningMessageWhenExceedGenerallySizeInComposer',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3930,4 +3930,18 @@ class AppLocalizations {
|
||||
name: 'continueAction',
|
||||
);
|
||||
}
|
||||
|
||||
String get thisFileCannotBePicked {
|
||||
return Intl.message(
|
||||
'This file cannot be picked.',
|
||||
name: 'thisFileCannotBePicked',
|
||||
);
|
||||
}
|
||||
|
||||
String get loadingPleaseWait {
|
||||
return Intl.message(
|
||||
'Loading... Please wait!',
|
||||
name: 'loadingPleaseWait',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class AppConfig {
|
||||
static const int limitCharToStartSearch = 3;
|
||||
static const int maximumMegabytesSizeFileAttachedInComposer = 10;
|
||||
static const int warningAttachmentFileSizeInMegabytes = 10;
|
||||
|
||||
static const String appDashboardConfigurationPath = "configurations/app_dashboard.json";
|
||||
static const String appFCMConfigurationPath = "configurations/env.fcm";
|
||||
|
||||
@@ -1,32 +1,65 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
class FileInfo with EquatableMixin {
|
||||
final String fileName;
|
||||
final String filePath;
|
||||
final int fileSize;
|
||||
final String? filePath;
|
||||
final Uint8List? bytes;
|
||||
final Stream<List<int>>? readStream;
|
||||
final String? type;
|
||||
final bool? isInline;
|
||||
final bool? isShared;
|
||||
|
||||
FileInfo(this.fileName, this.filePath, this.fileSize, {this.bytes});
|
||||
|
||||
factory FileInfo.empty() {
|
||||
return FileInfo('', '', 0);
|
||||
}
|
||||
FileInfo({
|
||||
required this.fileName,
|
||||
required this.fileSize,
|
||||
this.filePath,
|
||||
this.bytes,
|
||||
this.readStream,
|
||||
this.type,
|
||||
this.isInline,
|
||||
this.isShared,
|
||||
});
|
||||
|
||||
factory FileInfo.fromBytes({
|
||||
required Uint8List bytes,
|
||||
String? name,
|
||||
int? size
|
||||
int? size,
|
||||
String? type,
|
||||
}) {
|
||||
return FileInfo(name ?? '', '', size ?? 0, bytes: bytes);
|
||||
return FileInfo(
|
||||
fileName: name ?? '',
|
||||
fileSize: size ?? 0,
|
||||
bytes: bytes,
|
||||
type: type
|
||||
);
|
||||
}
|
||||
|
||||
String get fileExtension => fileName.split('.').last;
|
||||
|
||||
String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/octet-stream';
|
||||
String get mimeType {
|
||||
if (type?.isNotEmpty == true) {
|
||||
return type!;
|
||||
} else if (filePath?.isNotEmpty == true){
|
||||
final matchedType = lookupMimeType(filePath!, headerBytes: bytes) ?? 'application/octet-stream';
|
||||
return matchedType;
|
||||
} else {
|
||||
final matchedType = lookupMimeType(fileName, headerBytes: bytes) ?? 'application/octet-stream';
|
||||
return matchedType;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [fileName, filePath, fileSize, bytes];
|
||||
List<Object?> get props => [
|
||||
fileName,
|
||||
filePath,
|
||||
fileSize,
|
||||
bytes,
|
||||
readStream,
|
||||
type,
|
||||
isInline,
|
||||
isShared,
|
||||
];
|
||||
}
|
||||
@@ -360,6 +360,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: desktop_drop
|
||||
sha256: d55a010fe46c8e8fcff4ea4b451a9ff84a162217bdb3b2a0aa1479776205e15d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.4"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -990,6 +998,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
future_loading_dialog:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: future_loading_dialog
|
||||
sha256: "2718b1a308db452da32ab9bca9ad496ff92b683e217add9e92cf50520f90537e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
get:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -240,6 +240,10 @@ dependencies:
|
||||
|
||||
mime: 1.0.4
|
||||
|
||||
desktop_drop: 0.4.4
|
||||
|
||||
future_loading_dialog: 0.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart';
|
||||
|
||||
void main() {
|
||||
final attachmentA = Attachment(
|
||||
@@ -34,7 +34,7 @@ void main() {
|
||||
'WHEN perform call `attachmentA.isDisplayedPDFIcon()`\n'
|
||||
'SHOULD return true',
|
||||
() {
|
||||
bool result = attachmentA.isDisplayedPDFIcon;
|
||||
bool result = attachmentA.type!.validatePDFIcon(fileName: null);
|
||||
|
||||
expect(result, isTrue);
|
||||
});
|
||||
@@ -45,7 +45,7 @@ void main() {
|
||||
'WHEN perform call `attachmentB.isDisplayedPDFIcon()`\n'
|
||||
'SHOULD return true',
|
||||
() {
|
||||
bool result = attachmentB.isDisplayedPDFIcon;
|
||||
bool result = attachmentB.type!.validatePDFIcon(fileName: 'attachmentB.pdf');
|
||||
|
||||
expect(result, isTrue);
|
||||
});
|
||||
@@ -56,7 +56,7 @@ void main() {
|
||||
'WHEN perform call `attachmentC.isDisplayedPDFIcon()`\n'
|
||||
'SHOULD return false',
|
||||
() {
|
||||
bool result = attachmentC.isDisplayedPDFIcon;
|
||||
bool result = attachmentC.type!.validatePDFIcon(fileName: 'attachmentC.docx');
|
||||
|
||||
expect(result, isFalse);
|
||||
});
|
||||
@@ -67,7 +67,7 @@ void main() {
|
||||
'WHEN perform call `attachmentD.isDisplayedPDFIcon()`\n'
|
||||
'SHOULD return false',
|
||||
() {
|
||||
bool result = attachmentD.isDisplayedPDFIcon;
|
||||
bool result = attachmentD.type!.validatePDFIcon(fileName: 'attachmentD.png');
|
||||
|
||||
expect(result, isFalse);
|
||||
});
|
||||
@@ -78,7 +78,7 @@ void main() {
|
||||
'WHEN perform call `attachmentE.isDisplayedPDFIcon()`\n'
|
||||
'SHOULD return false',
|
||||
() {
|
||||
bool result = attachmentE.isDisplayedPDFIcon;
|
||||
bool result = attachmentE.type!.validatePDFIcon(fileName: 'attachmentE.pdf');
|
||||
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user