TF-2116 Insert image at mouse cursor position

(cherry picked from commit cb347e5077eddb1366daf706aa87eb2291f71aab)
This commit is contained in:
dab246
2023-09-08 11:41:35 +07:00
committed by Dat H. Pham
parent d0744e7560
commit e63a8f7d7f
11 changed files with 164 additions and 53 deletions
@@ -10,11 +10,24 @@ class DownloadImageAsBase64Success extends UIState {
final String base64Uri; final String base64Uri;
final String cid; final String cid;
final FileInfo fileInfo; final FileInfo fileInfo;
final bool fromFileShared;
DownloadImageAsBase64Success(this.base64Uri, this.cid, this.fileInfo); DownloadImageAsBase64Success(
this.base64Uri,
this.cid,
this.fileInfo,
{
this.fromFileShared = false
}
);
@override @override
List<Object?> get props => [base64Uri, cid, fileInfo]; List<Object?> get props => [
base64Uri,
cid,
fileInfo,
fromFileShared,
];
} }
class DownloadImageAsBase64Failure extends FeatureFailure { class DownloadImageAsBase64Failure extends FeatureFailure {
@@ -7,18 +7,40 @@ class UploadAttachmentSuccess extends UIState {
final UploadAttachment uploadAttachment; final UploadAttachment uploadAttachment;
final bool isInline; final bool isInline;
final bool fromFileShared;
UploadAttachmentSuccess(this.uploadAttachment, {this.isInline = false}); UploadAttachmentSuccess(
this.uploadAttachment,
{
this.isInline = false,
this.fromFileShared = false,
}
);
@override @override
List<Object?> get props => [uploadAttachment, isInline]; List<Object?> get props => [
uploadAttachment,
isInline,
fromFileShared,
];
} }
class UploadAttachmentFailure extends FeatureFailure { class UploadAttachmentFailure extends FeatureFailure {
final bool isInline; final bool isInline;
final bool fromFileShared;
UploadAttachmentFailure(dynamic exception, {this.isInline = false}) : super(exception: exception); UploadAttachmentFailure(
dynamic exception,
{
this.isInline = false,
this.fromFileShared = false,
}
) : super(exception: exception);
@override @override
List<Object?> get props => [isInline, ...super.props]; List<Object?> get props => [
isInline,
fromFileShared,
...super.props
];
} }
@@ -10,24 +10,31 @@ class DownloadImageAsBase64Interactor {
DownloadImageAsBase64Interactor(this._composerRepository); DownloadImageAsBase64Interactor(this._composerRepository);
Stream<Either<Failure, Success>> execute( Stream<Either<Failure, Success>> execute(
String url, String url,
String cid, String cid,
FileInfo fileInfo, FileInfo fileInfo,
{ {
double? maxWidth, double? maxWidth,
bool? compress bool? compress,
} bool fromFileShared = false,
}
) async* { ) async* {
try { try {
yield Right<Failure, Success>(DownloadingImageAsBase64()); yield Right<Failure, Success>(DownloadingImageAsBase64());
final result = await _composerRepository.downloadImageAsBase64( final result = await _composerRepository.downloadImageAsBase64(
url, url,
cid,
fileInfo,
maxWidth: maxWidth,
compress: compress
);
if (result?.isNotEmpty == true) {
yield Right<Failure, Success>(DownloadImageAsBase64Success(
result!,
cid, cid,
fileInfo, fileInfo,
maxWidth: maxWidth, fromFileShared: fromFileShared
compress: compress); ));
if (result?.isNotEmpty == true) {
yield Right<Failure, Success>(DownloadImageAsBase64Success(result!, cid, fileInfo));
} else { } else {
yield Left<Failure, Success>(DownloadImageAsBase64Failure(null)); yield Left<Failure, Success>(DownloadImageAsBase64Failure(null));
} }
@@ -15,7 +15,8 @@ class UploadAttachmentInteractor {
FileInfo fileInfo, FileInfo fileInfo,
Uri uploadUri, { Uri uploadUri, {
CancelToken? cancelToken, CancelToken? cancelToken,
bool isInline = false bool isInline = false,
bool fromFileShared = false,
}) async* { }) async* {
try { try {
final uploadAttachment = await _composerRepository.uploadAttachment( final uploadAttachment = await _composerRepository.uploadAttachment(
@@ -23,9 +24,17 @@ class UploadAttachmentInteractor {
uploadUri, uploadUri,
cancelToken: cancelToken cancelToken: cancelToken
); );
yield Right<Failure, Success>(UploadAttachmentSuccess(uploadAttachment, isInline: isInline)); yield Right<Failure, Success>(UploadAttachmentSuccess(
uploadAttachment,
isInline: isInline,
fromFileShared: fromFileShared
));
} catch (e) { } catch (e) {
yield Left<Failure, Success>(UploadAttachmentFailure(e, isInline: isInline)); yield Left<Failure, Success>(UploadAttachmentFailure(
e,
isInline: isInline,
fromFileShared: fromFileShared
));
} }
} }
} }
@@ -311,7 +311,10 @@ class ComposerController extends BaseController {
ImageSource.local, ImageSource.local,
fileInfo: success.fileInfo, fileInfo: success.fileInfo,
cid: success.cid, cid: success.cid,
base64Uri: success.base64Uri)); base64Uri: success.base64Uri
),
fromFileShare: success.fromFileShared
);
} }
maxWithEditor = null; maxWithEditor = null;
} }
@@ -1235,7 +1238,7 @@ class ComposerController extends BaseController {
if (listImageSharedMediaFile.isNotEmpty) { if (listImageSharedMediaFile.isNotEmpty) {
final listInlineImage = covertListSharedMediaFileToInlineImage(listSharedMediaFile); final listInlineImage = covertListSharedMediaFileToInlineImage(listSharedMediaFile);
for (var e in listInlineImage) { for (var e in listInlineImage) {
_uploadInlineAttachmentsAction(e.fileInfo!); _uploadInlineAttachmentsAction(e.fileInfo!, fromFileShared: true);
} }
} }
if (listFileAttachmentSharedMediaFile.isNotEmpty) { if (listFileAttachmentSharedMediaFile.isNotEmpty) {
@@ -1681,13 +1684,18 @@ class ComposerController extends BaseController {
} }
} }
void _uploadInlineAttachmentsAction(FileInfo pickedFile) async { void _uploadInlineAttachmentsAction(FileInfo pickedFile, {bool fromFileShared = false}) async {
if (uploadController.hasEnoughMaxAttachmentSize(listFiles: [pickedFile])) { if (uploadController.hasEnoughMaxAttachmentSize(listFiles: [pickedFile])) {
final session = mailboxDashBoardController.sessionCurrent; final session = mailboxDashBoardController.sessionCurrent;
final accountId = mailboxDashBoardController.accountId.value; final accountId = mailboxDashBoardController.accountId.value;
if (session != null && accountId != null) { if (session != null && accountId != null) {
final uploadUri = session.getUploadUri(accountId, jmapUrl: _dynamicUrlInterceptors.jmapUrl); final uploadUri = session.getUploadUri(accountId, jmapUrl: _dynamicUrlInterceptors.jmapUrl);
uploadController.uploadFileAction(pickedFile, uploadUri, isInline: true); uploadController.uploadFileAction(
pickedFile,
uploadUri,
isInline: true,
fromFileShared: fromFileShared
);
} }
} else { } else {
if (currentContext != null) { if (currentContext != null) {
@@ -1713,10 +1721,12 @@ class ComposerController extends BaseController {
final imageUrl = uploadState.attachment.getDownloadUrl(baseDownloadUrl, accountId); final imageUrl = uploadState.attachment.getDownloadUrl(baseDownloadUrl, accountId);
log('ComposerController::_handleUploadInlineSuccess(): imageUrl: $imageUrl'); log('ComposerController::_handleUploadInlineSuccess(): imageUrl: $imageUrl');
consumeState(_downloadImageAsBase64Interactor.execute( consumeState(_downloadImageAsBase64Interactor.execute(
imageUrl, imageUrl,
uploadState.attachment.cid!, uploadState.attachment.cid!,
uploadState.fileInfo, uploadState.fileInfo,
maxWidth: maxWithEditor)); maxWidth: maxWithEditor,
fromFileShared: uploadState.fromFileShared,
));
} }
} }
@@ -9,12 +9,20 @@ import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.
class RichTextMobileTabletController extends BaseRichTextController { class RichTextMobileTabletController extends BaseRichTextController {
HtmlEditorApi? htmlEditorApi; HtmlEditorApi? htmlEditorApi;
void insertImage(InlineImage image, {double? maxWithEditor}) async { void insertImage(
log('RichTextMobileTabletController::insertImage(): $image | maxWithEditor: $maxWithEditor'); InlineImage image,
{
double? maxWithEditor,
bool fromFileShare = false
}
) async {
log('RichTextMobileTabletController::insertImage(): $image | maxWithEditor: $maxWithEditor | $fromFileShare');
if (image.source == ImageSource.network) { if (image.source == ImageSource.network) {
htmlEditorApi?.insertImageLink(image.link!); htmlEditorApi?.insertImageLink(image.link!);
} else { } else {
await htmlEditorApi?.moveCursorAtLastNode(); if (fromFileShare) {
await htmlEditorApi?.moveCursorAtLastNode();
}
await htmlEditorApi?.insertHtml(image.base64Uri ?? ''); await htmlEditorApi?.insertHtml(image.base64Uri ?? '');
} }
} }
@@ -31,11 +31,24 @@ class SuccessAttachmentUploadState extends Success {
final UploadTaskId uploadId; final UploadTaskId uploadId;
final Attachment attachment; final Attachment attachment;
final FileInfo fileInfo; final FileInfo fileInfo;
final bool fromFileShared;
SuccessAttachmentUploadState(this.uploadId, this.attachment, this.fileInfo); SuccessAttachmentUploadState(
this.uploadId,
this.attachment,
this.fileInfo,
{
this.fromFileShared = false
}
);
@override @override
List<Object?> get props => [uploadId, attachment, fileInfo]; List<Object?> get props => [
uploadId,
attachment,
fileInfo,
fromFileShared,
];
} }
class ErrorAttachmentUploadState extends Failure { class ErrorAttachmentUploadState extends Failure {
@@ -157,8 +157,13 @@ class UploadController extends BaseController {
} else if (success is SuccessAttachmentUploadState) { } else if (success is SuccessAttachmentUploadState) {
log('UploadController::_handleProgressUploadInlineImageStateStream():succeed[${success.uploadId}]'); log('UploadController::_handleProgressUploadInlineImageStateStream():succeed[${success.uploadId}]');
final inlineAttachment = success.attachment.toAttachmentWithDisposition( final inlineAttachment = success.attachment.toAttachmentWithDisposition(
disposition: ContentDisposition.inline, disposition: ContentDisposition.inline,
cid: _uuid.v1()); cid: _uuid.v1()
);
final uploadFileState = _uploadingStateInlineFiles.getUploadFileStateById(success.uploadId);
log('UploadController::_handleProgressUploadInlineImageStateStream:uploadId: ${uploadFileState?.uploadTaskId} | fromFileShared: ${uploadFileState?.fromFileShared}');
_uploadingStateInlineFiles.updateElementByUploadTaskId( _uploadingStateInlineFiles.updateElementByUploadTaskId(
success.uploadId, success.uploadId,
(currentState) { (currentState) {
@@ -173,7 +178,9 @@ class UploadController extends BaseController {
final newUploadSuccess = SuccessAttachmentUploadState( final newUploadSuccess = SuccessAttachmentUploadState(
success.uploadId, success.uploadId,
inlineAttachment, inlineAttachment,
success.fileInfo); success.fileInfo,
fromFileShared: uploadFileState?.fromFileShared ?? false
);
_handleUploadInlineAttachmentsSuccess(newUploadSuccess); _handleUploadInlineAttachmentsSuccess(newUploadSuccess);
} }
} }
@@ -202,13 +209,21 @@ class UploadController extends BaseController {
}); });
} }
Future<void> uploadFileAction(FileInfo uploadFile, Uri uploadUri, {bool isInline = false}) { Future<void> uploadFileAction(
log('UploadController::_uploadFile():fileName: ${uploadFile.fileName}'); FileInfo uploadFile,
Uri uploadUri,
{
bool isInline = false,
bool fromFileShared = false,
}
) {
log('UploadController::_uploadFile():fileName: ${uploadFile.fileName} | isInline: $isInline | fromFileShared: $fromFileShared');
consumeState(_uploadAttachmentInteractor.execute( consumeState(_uploadAttachmentInteractor.execute(
uploadFile, uploadFile,
uploadUri, uploadUri,
cancelToken: CancelToken(), cancelToken: CancelToken(),
isInline: isInline isInline: isInline,
fromFileShared: fromFileShared
)); ));
return Future.value(); return Future.value();
} }
@@ -368,7 +383,7 @@ class UploadController extends BaseController {
super.handleSuccessViewState(success); super.handleSuccessViewState(success);
if (success is UploadAttachmentSuccess) { if (success is UploadAttachmentSuccess) {
if (success.isInline) { if (success.isInline) {
_uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState()); _uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState(fromFileShared: success.fromFileShared));
await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState); await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
} else { } else {
_uploadingStateFiles.add(success.uploadAttachment.toUploadFileState()); _uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
@@ -4,11 +4,12 @@ import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_sta
extension UploadAttachmentExtension on UploadAttachment { extension UploadAttachmentExtension on UploadAttachment {
UploadFileState toUploadFileState() { UploadFileState toUploadFileState({bool fromFileShared = false}) {
return UploadFileState( return UploadFileState(
uploadTaskId, uploadTaskId,
file: fileInfo, file: fileInfo,
cancelToken: cancelToken cancelToken: cancelToken,
fromFileShared: fromFileShared,
); );
} }
} }
@@ -18,14 +18,19 @@ class UploadFileState with EquatableMixin {
final int uploadingProgress; final int uploadingProgress;
final Attachment? attachment; final Attachment? attachment;
final CancelToken? cancelToken; final CancelToken? cancelToken;
final bool fromFileShared;
UploadFileState(this.uploadTaskId, { UploadFileState(
this.file, this.uploadTaskId,
this.uploadStatus = UploadFileStatus.waiting, {
this.uploadingProgress = 0, this.file,
this.attachment, this.uploadStatus = UploadFileStatus.waiting,
this.cancelToken, this.uploadingProgress = 0,
}); this.attachment,
this.cancelToken,
this.fromFileShared = false,
}
);
UploadFileState copyWith({ UploadFileState copyWith({
UploadTaskId? uploadTaskId, UploadTaskId? uploadTaskId,
@@ -33,7 +38,8 @@ class UploadFileState with EquatableMixin {
UploadFileStatus? uploadStatus, UploadFileStatus? uploadStatus,
int? uploadingProgress, int? uploadingProgress,
Attachment? attachment, Attachment? attachment,
CancelToken? cancelToken CancelToken? cancelToken,
bool? fromFileShared,
}) { }) {
return UploadFileState( return UploadFileState(
uploadTaskId ?? this.uploadTaskId, uploadTaskId ?? this.uploadTaskId,
@@ -41,7 +47,8 @@ class UploadFileState with EquatableMixin {
uploadStatus: uploadStatus ?? this.uploadStatus, uploadStatus: uploadStatus ?? this.uploadStatus,
uploadingProgress: uploadingProgress ?? this.uploadingProgress, uploadingProgress: uploadingProgress ?? this.uploadingProgress,
attachment: attachment ?? this.attachment, attachment: attachment ?? this.attachment,
cancelToken: cancelToken ?? this.cancelToken cancelToken: cancelToken ?? this.cancelToken,
fromFileShared: fromFileShared ?? this.fromFileShared
); );
} }
@@ -96,6 +103,8 @@ class UploadFileState with EquatableMixin {
file, file,
uploadStatus, uploadStatus,
uploadingProgress, uploadingProgress,
attachment attachment,
cancelToken,
fromFileShared,
]; ];
} }
@@ -66,4 +66,8 @@ class UploadFileStateList {
_uploadingStateFiles.remove(fileState); _uploadingStateFiles.remove(fileState);
} }
} }
UploadFileState? getUploadFileStateById(UploadTaskId uploadTaskId) {
return _uploadingStateFiles.firstWhereOrNull((fileState) => fileState?.uploadTaskId == uploadTaskId);
}
} }