diff --git a/assets/images/ic_insert_image.svg b/assets/images/ic_insert_image.svg
new file mode 100644
index 000000000..07da9c41b
--- /dev/null
+++ b/assets/images/ic_insert_image.svg
@@ -0,0 +1,3 @@
+
diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart
index 6eb5c819a..449201dde 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -131,6 +131,7 @@ class ImagePaths {
String get icStyleBold => _getImagePath('ic_style_bold.svg');
String get icStyleItalic => _getImagePath('ic_style_italic.svg');
String get icStyleUnderline => _getImagePath('ic_style_underline.svg');
+ String get icInsertImage => _getImagePath('ic_insert_image.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
diff --git a/core/lib/presentation/views/button/icon_button_web.dart b/core/lib/presentation/views/button/icon_button_web.dart
index d476ccf36..e5d8c8e2e 100644
--- a/core/lib/presentation/views/button/icon_button_web.dart
+++ b/core/lib/presentation/views/button/icon_button_web.dart
@@ -125,3 +125,37 @@ Widget buildTextButton(String text, {
),
);
}
+
+Widget buildButtonWrapText(String name, {
+ TextStyle? textStyle,
+ Color? bgColor,
+ double? radius,
+ double? height,
+ EdgeInsets? padding,
+ IconWebCallback? onTap
+}) {
+ return Container(
+ height: height ?? 40,
+ padding: padding,
+ child: ElevatedButton(
+ onPressed: () => onTap?.call(),
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.resolveWith(
+ (Set states) => bgColor ?? AppColor.colorTextButton),
+ shape: MaterialStateProperty.all(RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(radius ?? 8),
+ side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton))),
+ padding: MaterialStateProperty.resolveWith(
+ (Set states) => const EdgeInsets.symmetric(horizontal: 16)),
+ elevation: MaterialStateProperty.resolveWith(
+ (Set states) => 0)),
+ child: Text(name,
+ textAlign: TextAlign.center,
+ style: textStyle ??
+ const TextStyle(
+ fontSize: 17,
+ fontWeight: FontWeight.w500,
+ color: Colors.white)),
+ ),
+ );
+}
\ No newline at end of file
diff --git a/lib/features/base/mixin/app_loader_mixin.dart b/lib/features/base/mixin/app_loader_mixin.dart
index ff0f5225c..220ad24c0 100644
--- a/lib/features/base/mixin/app_loader_mixin.dart
+++ b/lib/features/base/mixin/app_loader_mixin.dart
@@ -31,4 +31,12 @@ mixin AppLoaderMixin {
progressColor: AppColor.primaryColor,
));
}
+
+ Widget loadingWidgetWithSizeColor({double? size, Color? color}) {
+ return Center(child: SizedBox(
+ width: size ?? 24,
+ height: size ?? 24,
+ child: CircularProgressIndicator(
+ color: color ?? AppColor.colorLoading)));
+ }
}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart
index b6382b5c7..19d04e586 100644
--- a/lib/features/composer/presentation/composer_bindings.dart
+++ b/lib/features/composer/presentation/composer_bindings.dart
@@ -3,7 +3,9 @@ import 'package:core/core.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
+import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart';
import 'package:tmail_ui_user/features/composer/data/datasource/contact_datasource.dart';
+import 'package:tmail_ui_user/features/composer/data/datasource_impl/composer_datasource_impl.dart';
import 'package:tmail_ui_user/features/composer/data/datasource_impl/contact_datasource_impl.dart';
import 'package:tmail_ui_user/features/composer/data/repository/auto_complete_repository_impl.dart';
import 'package:tmail_ui_user/features/composer/data/repository/composer_repository_impl.dart';
@@ -11,6 +13,7 @@ import 'package:tmail_ui_user/features/composer/data/repository/contact_reposito
import 'package:tmail_ui_user/features/composer/domain/repository/auto_complete_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart';
+import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_as_drafts_interactor.dart';
@@ -74,6 +77,7 @@ class ComposerBindings extends BaseBindings {
}
Get.lazyPut(() => AttachmentUploadDataSourceImpl(Get.find()));
+ Get.lazyPut(() => ComposerDataSourceImpl(Get.find()));
Get.lazyPut(() => ContactDataSourceImpl());
Get.lazyPut(() => EmailDataSourceImpl(Get.find()));
Get.lazyPut(() => HtmlDataSourceImpl(
@@ -88,6 +92,7 @@ class ComposerBindings extends BaseBindings {
@override
void bindingsDataSource() {
Get.lazyPut(() => Get.find());
+ Get.lazyPut(() => Get.find());
Get.lazyPut(() => Get.find());
Get.lazyPut(() => Get.find());
Get.lazyPut(() => Get.find());
@@ -96,7 +101,9 @@ class ComposerBindings extends BaseBindings {
@override
void bindingsRepositoryImpl() {
- Get.lazyPut(() => ComposerRepositoryImpl(Get.find()));
+ Get.lazyPut(() => ComposerRepositoryImpl(
+ Get.find(),
+ Get.find()));
Get.lazyPut(() => AutoCompleteRepositoryImpl(dataSources));
Get.lazyPut(() => ContactRepositoryImpl(Get.find()));
Get.lazyPut(() => EmailRepositoryImpl(
@@ -132,6 +139,7 @@ class ComposerBindings extends BaseBindings {
Get.lazyPut(() => GetAllIdentitiesInteractor(Get.find()));
Get.lazyPut(() => RemoveComposerCacheOnWebInteractor(Get.find()));
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(Get.find()));
+ Get.lazyPut(() => DownloadImageAsBase64Interactor(Get.find()));
}
@override
@@ -153,6 +161,7 @@ class ComposerBindings extends BaseBindings {
Get.find(),
Get.find(),
Get.find(),
+ Get.find(),
));
}
diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart
index a78e91068..adec96dc7 100644
--- a/lib/features/composer/presentation/composer_controller.dart
+++ b/lib/features/composer/presentation/composer_controller.dart
@@ -27,7 +27,9 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart';
+import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart';
+import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_as_drafts_interactor.dart';
@@ -36,7 +38,10 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/update_email_dra
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.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/screen_display_mode.dart';
+import 'package:tmail_ui_user/features/composer/presentation/widgets/insert_image_dialog_builder.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
@@ -46,6 +51,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller
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/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/usecases/local_file_picker_interactor.dart';
import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.dart';
@@ -90,6 +96,7 @@ class ComposerController extends BaseController {
final RemoveComposerCacheOnWebInteractor _removeComposerCacheOnWebInteractor;
final SaveComposerCacheOnWebInteractor _saveComposerCacheOnWebInteractor;
final RichTextWebController richTextWebController;
+ final DownloadImageAsBase64Interactor _downloadImageAsBase64Interactor;
List listToEmailAddress = [];
List listCcEmailAddress = [];
@@ -104,6 +111,8 @@ class ComposerController extends BaseController {
List initialAttachments = [];
String? _textEditorWeb;
List? _emailContents;
+ double? maxWithEditor;
+ late Worker uploadInlineImageWorker;
void setTextEditorWeb(String? text) => _textEditorWeb = text;
@@ -159,11 +168,13 @@ class ComposerController extends BaseController {
this._removeComposerCacheOnWebInteractor,
this._saveComposerCacheOnWebInteractor,
this.richTextWebController,
+ this._downloadImageAsBase64Interactor,
);
@override
void onInit() {
super.onInit();
+ _listenWorker();
if (!BuildUtils.isWeb) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await FkUserAgent.init();
@@ -200,6 +211,7 @@ class ComposerController extends BaseController {
toEmailAddressController.dispose();
ccEmailAddressController.dispose();
bccEmailAddressController.dispose();
+ uploadInlineImageWorker.dispose();
super.dispose();
}
@@ -228,13 +240,22 @@ class ComposerController extends BaseController {
_pickFileSuccess(success);
} else if (success is GetEmailContentSuccess) {
_getEmailContentSuccess(success);
- } if (success is GetAllIdentitiesSuccess) {
+ } else if (success is GetAllIdentitiesSuccess) {
if (success.identities?.isNotEmpty == true) {
listIdentities.value = success.identities!
.where((identity) => identity.mayDelete == true)
.toList();
selectIdentity(listIdentities.first);
}
+ } else if (success is DownloadImageAsBase64Success) {
+ richTextWebController.insertImage(
+ InlineImage(
+ ImageSource.local,
+ fileInfo: success.fileInfo,
+ cid: success.cid,
+ base64: success.imageAsBase64),
+ maxWithEditor: maxWithEditor);
+ maxWithEditor = null;
}
});
}
@@ -251,6 +272,19 @@ class ComposerController extends BaseController {
popBack();
}
+ void _listenWorker() {
+ uploadInlineImageWorker = ever(uploadController.uploadInlineViewState, (state) {
+ log('ComposerController::_listenWorker(): $state');
+ if (state is Either) {
+ state.fold((failure) => null, (success) {
+ if (success is SuccessAttachmentUploadState) {
+ _handleUploadInlineSuccess(success);
+ }
+ });
+ }
+ });
+ }
+
void _listenBrowserTabRefresh() {
html.window.onBeforeUnload.listen((event) async {
final userProfile = mailboxDashBoardController.userProfile.value;
@@ -497,7 +531,21 @@ class ComposerController extends BaseController {
final generateBlobId = Id(_uuid.v1());
var emailBodyText = await _getEmailBodyText(context);
- log('ComposerController::_generateEmail(): $emailBodyText');
+ final mapContents = await richTextWebController.refactorContentHasInlineImage(
+ emailBodyText,
+ uploadController.mapInlineAttachments);
+ log('ComposerController::_generateEmail(): mapContents: $mapContents');
+ emailBodyText = mapContents.value1;
+ final listInlineAttachment = mapContents.value2;
+ final listInlineEmailBodyPart = listInlineAttachment
+ .map((attachment) => attachment.toEmailBodyPart(charset: 'base64'))
+ .toSet();
+
+ final attachments = {};
+ attachments.addAll(uploadController.generateAttachments() ?? []);
+ attachments.addAll(listInlineEmailBodyPart);
+ log('ComposerController::_generateEmail(): listInlineEmailBodyPart: $listInlineEmailBodyPart');
+
final userAgent = await userAgentPlatform;
log('ComposerController::_generateEmail(): userAgent: $userAgent');
@@ -521,7 +569,7 @@ class ComposerController extends BaseController {
generatePartId: EmailBodyValue(emailBodyText, false, false)
},
headerUserAgent: {IndividualHeaderIdentifier.headerUserAgent : userAgent},
- attachments: uploadController.generateAttachments(),
+ attachments: attachments.isNotEmpty ? attachments : null,
);
}
@@ -623,7 +671,7 @@ class ComposerController extends BaseController {
final email = await _generateEmail(context, mapDefaultMailboxId, userProfile);
final accountId = session.accounts.keys.first;
final sentMailboxId = mapDefaultMailboxId[PresentationMailbox.roleSent];
- final submissionCreateId = Id(const Uuid().v1());
+ final submissionCreateId = Id(_uuid.v1());
mailboxDashBoardController.consumeState(_sendEmailInteractor.execute(
accountId,
@@ -631,6 +679,8 @@ class ComposerController extends BaseController {
emailIdDestroyed: arguments.emailActionType == EmailActionType.edit
? arguments.presentationEmail?.id
: null)));
+
+ uploadController.clearInlineFileUploaded();
}
if (kIsWeb) {
@@ -1055,6 +1105,65 @@ class ComposerController extends BaseController {
}
}
+ void insertImage(BuildContext context, {double? maxWithEditor}) async {
+ await InsertImageDialogBuilder(
+ context,
+ insertActionCallback: (inlineImage) {
+ log('ComposerController::insertImage(): ${inlineImage.source}|$maxWithEditor');
+ if (BuildUtils.isWeb) {
+ this.maxWithEditor = maxWithEditor;
+ _insertImageOnWeb(inlineImage);
+ }
+ }
+ ).show();
+ }
+
+ void _insertImageOnWeb(InlineImage inlineImage) {
+ if (inlineImage.source == ImageSource.local) {
+ _uploadInlineAttachmentsAction(inlineImage.fileInfo!);
+ } else {
+ richTextWebController.insertImage(inlineImage);
+ }
+ }
+
+ void _uploadInlineAttachmentsAction(FileInfo pickedFile) async {
+ if (uploadController.hasEnoughMaxAttachmentSize(listFiles: [pickedFile])) {
+ final session = mailboxDashBoardController.sessionCurrent;
+ final accountId = mailboxDashBoardController.accountId.value;
+ if (session != null && accountId != null) {
+ final uploadUri = session.getUploadUri(accountId);
+ uploadController.uploadInlineImage(pickedFile, uploadUri);
+ }
+ } 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,
+ () => {},
+ title: AppLocalizations.of(currentContext!).maximum_files_size,
+ hasCancelButton: false);
+ }
+ }
+ }
+
+ void _handleUploadInlineSuccess(SuccessAttachmentUploadState uploadState) {
+ uploadController.clearUploadInlineViewState();
+
+ final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl();
+ final accountId = mailboxDashBoardController.accountId.value;
+
+ if (baseDownloadUrl != null && accountId != null) {
+ final imageUrl = uploadState.attachment.getDownloadUrl(baseDownloadUrl, accountId);
+ log('ComposerController::_handleUploadInlineSuccess(): imageUrl: $imageUrl');
+ consumeState(_downloadImageAsBase64Interactor.execute(
+ imageUrl,
+ uploadState.attachment.cid!,
+ uploadState.fileInfo));
+ }
+ }
+
void closeComposer() {
FocusManager.instance.primaryFocus?.unfocus();
popBack();
diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart
index 58a2b230f..a28e6f71e 100644
--- a/lib/features/composer/presentation/composer_view.dart
+++ b/lib/features/composer/presentation/composer_view.dart
@@ -3,7 +3,6 @@ import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:enough_html_editor/enough_html_editor.dart';
import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart';
-import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -11,7 +10,6 @@ import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
-import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart';
@@ -412,7 +410,6 @@ class ComposerView extends GetWidget with AppLoaderMixin, Ri
child: _buildAttachmentsTitle(context,
uploadAttachments,
controller.expandModeAttachments.value)),
- _buildAttachmentsLoadingView(),
Padding(
padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
child: _buildAttachmentsList(context,
@@ -479,7 +476,6 @@ class ComposerView extends GetWidget with AppLoaderMixin, Ri
child: _buildAttachmentsTitle(context,
uploadAttachments,
controller.expandModeAttachments.value)),
- _buildAttachmentsLoadingView(),
Padding(
padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
child: _buildAttachmentsList(context,
@@ -589,19 +585,6 @@ class ComposerView extends GetWidget with AppLoaderMixin, Ri
});
}
- Widget _buildAttachmentsLoadingView({EdgeInsets? padding, double? size}) {
- return Obx(() => controller.viewState.value.fold(
- (failure) => const SizedBox.shrink(),
- (success) => success is UploadingAttachmentState
- ? Center(child: Padding(
- padding: padding ?? const EdgeInsets.all(10),
- child: SizedBox(
- width: size ?? 20,
- height: size ?? 20,
- child: const CupertinoActivityIndicator(color: AppColor.colorTextButton))))
- : const SizedBox.shrink()));
- }
-
Widget _buildAttachmentsTitle(
BuildContext context,
List uploadFilesState,
@@ -612,7 +595,6 @@ class ComposerView extends GetWidget with AppLoaderMixin, Ri
Text(
'${AppLocalizations.of(context).attachments} (${filesize(uploadFilesState.totalSize, 0)}):',
style: const TextStyle(fontSize: 12, color: AppColor.colorHintEmailAddressInput, fontWeight: FontWeight.normal)),
- _buildAttachmentsLoadingView(padding: const EdgeInsets.only(left: 16), size: 16),
const Spacer(),
Material(
type: MaterialType.circle,
diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart
index ca94557ca..eb600799e 100644
--- a/lib/features/composer/presentation/composer_view_web.dart
+++ b/lib/features/composer/presentation/composer_view_web.dart
@@ -2,7 +2,6 @@ import 'package:core/core.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart';
-import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
@@ -11,13 +10,14 @@ import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:model/model.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
-import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
+import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_file_composer_builder.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/email_address_input_builder.dart';
+import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/extensions/list_upload_file_state_extension.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
@@ -54,14 +54,19 @@ class ComposerView extends GetWidget
: const SizedBox.shrink()),
_buildEmailAddress(context, constraints),
const Divider(color: AppColor.colorDividerComposer, height: 1),
- Padding(padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildSubjectEmail(context)),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: _buildSubjectEmail(context)),
const Divider(color: AppColor.colorDividerComposer, height: 1),
- Padding(padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildListButton(context)),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: _buildListButton(context, constraints)),
const Divider(color: AppColor.colorDividerComposer, height: 1),
Expanded(child: Column(
children: [
_buildAttachmentsWidget(context),
_buildToolbarRichTextWidget(),
+ _buildInlineLoadingView(),
_buildEditorForm(context)
]
)),
@@ -282,7 +287,7 @@ class ComposerView extends GetWidget
const Divider(color: AppColor.colorDividerComposer, height: 1),
Padding(padding: const EdgeInsets.only(right: 16), child: _buildSubjectEmail(context)),
const Divider(color: AppColor.colorDividerComposer, height: 1),
- _buildListButton(context),
+ _buildListButton(context, constraints),
]),
))
])),
@@ -295,6 +300,7 @@ class ComposerView extends GetWidget
children: [
_buildAttachmentsWidget(context),
_buildToolbarRichTextWidget(),
+ _buildInlineLoadingView(),
_buildEditorForm(context)
]
))),
@@ -483,7 +489,7 @@ class ComposerView extends GetWidget
);
}
- Widget _buildListButton(BuildContext context) {
+ Widget _buildListButton(BuildContext context, BoxConstraints constraints) {
return Transform(
transform: Matrix4.translationValues(-5.0, 0.0, 0.0),
child: Padding(
@@ -499,6 +505,17 @@ class ComposerView extends GetWidget
fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).attach_file,
onTap: () => controller.openFilePickerByType(context, FileType.any)),
+ const SizedBox(width: 4),
+ buildIconWeb(
+ minSize: 40,
+ iconPadding: EdgeInsets.zero,
+ icon: SvgPicture.asset(imagePaths.icInsertImage,
+ color: AppColor.colorTextButton,
+ fit: BoxFit.fill),
+ tooltip: AppLocalizations.of(context).insertImage,
+ onTap: () => controller.insertImage(
+ context,
+ maxWithEditor: constraints.maxWidth - 120)),
])
)
);
@@ -604,7 +621,6 @@ class ComposerView extends GetWidget
)
)
);
- // });
}
Widget _buildAttachmentsWidget(BuildContext context) {
@@ -622,7 +638,6 @@ class ComposerView extends GetWidget
context,
attachments,
controller.expandModeAttachments.value)),
- _buildAttachmentsLoadingView(),
Padding(
padding: EdgeInsets.only(
bottom: 8,
@@ -639,19 +654,6 @@ class ComposerView extends GetWidget
});
}
- Widget _buildAttachmentsLoadingView({EdgeInsets? padding, double? size}) {
- return Obx(() => controller.viewState.value.fold(
- (failure) => const SizedBox.shrink(),
- (success) => success is UploadingAttachmentState
- ? Center(child: Padding(
- padding: padding ?? const EdgeInsets.all(10),
- child: SizedBox(
- width: size ?? 20,
- height: size ?? 20,
- child: const CupertinoActivityIndicator(color: AppColor.colorTextButton))))
- : const SizedBox.shrink()));
- }
-
Widget _buildAttachmentsTitle(
BuildContext context,
List uploadFilesState,
@@ -661,7 +663,6 @@ class ComposerView extends GetWidget
Text(
'${AppLocalizations.of(context).attachments} (${filesize(uploadFilesState.totalSize, 0)}):',
style: const TextStyle(fontSize: 12, color: AppColor.colorHintEmailAddressInput, fontWeight: FontWeight.normal)),
- _buildAttachmentsLoadingView(padding: const EdgeInsets.only(left: 16), size: 16),
const Spacer(),
Material(
type: MaterialType.circle,
@@ -746,4 +747,20 @@ class ComposerView extends GetWidget
),
);
}
+
+ Widget _buildInlineLoadingView() {
+ return Obx(() => controller.uploadController.uploadInlineViewState.value.fold(
+ (failure) => const SizedBox.shrink(),
+ (success) {
+ if (success is UploadingAttachmentUploadState ||
+ success is DownloadingImageAsBase64) {
+ return Padding(
+ padding: const EdgeInsets.all(5),
+ child: loadingWidgetWithSizeColor(
+ size: 30,
+ color: AppColor.primaryColor));
+ }
+ return const SizedBox.shrink();
+ }));
+ }
}
\ No newline at end of file
diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb
index b7ee47594..c2a2645de 100644
--- a/lib/l10n/intl_messages.arb
+++ b/lib/l10n/intl_messages.arb
@@ -1,5 +1,5 @@
{
- "@@last_modified": "2022-07-11T12:17:57.489417",
+ "@@last_modified": "2022-07-18T16:31:40.598846",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -1573,5 +1573,47 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
+ },
+ "insertImage": "Insert image",
+ "@insertImage": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
+ },
+ "selectFromFile": "Select from file",
+ "@selectFromFile": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
+ },
+ "chooseImage": "Choose image",
+ "@chooseImage": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
+ },
+ "urlLink": "URL",
+ "@urlLink": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
+ },
+ "insert": "Insert",
+ "@insert": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
+ },
+ "insertImageErrorFileEmpty": "Please either choose an image or enter an image URL",
+ "@insertImageErrorFileEmpty": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
+ },
+ "insertImageErrorDuplicate": "Please input either an image or an image URL, not both",
+ "@insertImageErrorDuplicate": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
}
}
\ No newline at end of file
diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart
index df9d5d5e3..bac8f55a5 100644
--- a/lib/main/localizations/app_localizations.dart
+++ b/lib/main/localizations/app_localizations.dart
@@ -1614,4 +1614,49 @@ class AppLocalizations {
'Save & close',
name: 'saveAndClose');
}
+
+ String get insertImage {
+ return Intl.message(
+ 'Insert image',
+ name: 'insertImage');
+ }
+
+ String get selectFromFile {
+ return Intl.message(
+ 'Select from file',
+ name: 'selectFromFile',
+ );
+ }
+
+ String get chooseImage {
+ return Intl.message(
+ 'Choose image',
+ name: 'chooseImage',
+ );
+ }
+
+ String get urlLink {
+ return Intl.message(
+ 'URL',
+ name: 'urlLink',
+ );
+ }
+
+ String get insert {
+ return Intl.message(
+ 'Insert',
+ name: 'insert');
+ }
+
+ String get insertImageErrorFileEmpty {
+ return Intl.message(
+ 'Please either choose an image or enter an image URL',
+ name: 'insertImageErrorFileEmpty');
+ }
+
+ String get insertImageErrorDuplicate {
+ return Intl.message(
+ 'Please input either an image or an image URL, not both',
+ name: 'insertImageErrorDuplicate');
+ }
}
\ No newline at end of file
diff --git a/model/lib/extensions/attachment_extension.dart b/model/lib/extensions/attachment_extension.dart
index 783b11dba..74c799e41 100644
--- a/model/lib/extensions/attachment_extension.dart
+++ b/model/lib/extensions/attachment_extension.dart
@@ -3,10 +3,28 @@ import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/model.dart';
extension AttachmentExtension on Attachment {
- EmailBodyPart toEmailBodyPart(String disposition) => EmailBodyPart(
+ EmailBodyPart toEmailBodyPart({String? disposition, String? charset}) => EmailBodyPart(
+ partId: partId,
blobId: blobId,
size: size,
name: name,
type: type,
- disposition: disposition);
+ cid: cid,
+ charset: charset,
+ disposition: disposition ?? this.disposition?.value);
+
+ Attachment toAttachmentWithDisposition({
+ ContentDisposition? disposition,
+ String? cid
+ }) {
+ return Attachment(
+ partId: partId,
+ blobId: blobId,
+ size: size,
+ name: name,
+ type: type,
+ cid: cid ?? this.cid,
+ disposition: disposition ?? this.disposition
+ );
+ }
}
\ No newline at end of file
diff --git a/model/lib/upload/file_info.dart b/model/lib/upload/file_info.dart
index c2fdcaefb..89183c30b 100644
--- a/model/lib/upload/file_info.dart
+++ b/model/lib/upload/file_info.dart
@@ -15,6 +15,8 @@ class FileInfo with EquatableMixin {
return FileInfo('', '', 0);
}
+ String get fileExtension => fileName.split('.').last;
+
String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/json; charset=UTF-8';
@override
diff --git a/pubspec.yaml b/pubspec.yaml
index 0349919bd..61434e5f6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -171,6 +171,9 @@ dependencies:
async: 2.8.2
+ # HTML5 parser
+ html: 0.15.0
+
dev_dependencies:
flutter_test:
sdk: flutter