TF-2116 Insert image at mouse cursor position
(cherry picked from commit cb347e5077eddb1366daf706aa87eb2291f71aab)
This commit is contained in:
@@ -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,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user