TF-725 Implement UI/UX for insert image to composer on web

This commit is contained in:
dab246
2022-07-20 16:51:16 +07:00
committed by Dat H. Pham
parent da32cf9a40
commit e6f1ced4cd
13 changed files with 321 additions and 48 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.3083 7C22.8978 7 24.1262 7.23723 25.4021 7.91958C26.5542 8.53571 27.4643 9.44582 28.0804 10.5979C28.7628 11.8738 29 13.1022 29 15.6917V20.3083C29 22.8978 28.7628 24.1262 28.0804 25.4021C27.4643 26.5542 26.5542 27.4643 25.4021 28.0804C24.1262 28.7628 22.8978 29 20.3083 29H15.6917C13.1022 29 11.8738 28.7628 10.5979 28.0804C9.44582 27.4643 8.53571 26.5542 7.91958 25.4021C7.23723 24.1262 7 22.8978 7 20.3083V15.6917C7 13.1022 7.23723 11.8738 7.91958 10.5979C8.53571 9.44582 9.44582 8.53571 10.5979 7.91958C11.8738 7.23723 13.1022 7 15.6917 7H20.3083ZM21.9791 19.4419L17.7771 24.6294C17.426 25.0628 16.7883 25.1253 16.3598 24.7682L14.0614 22.8528L10.9632 25.9516C11.1441 26.0857 11.3368 26.2076 11.5411 26.3168C12.4876 26.823 13.4042 27 15.6917 27H20.3083C22.5958 27 23.5124 26.823 24.4589 26.3168C25.2624 25.8871 25.8871 25.2624 26.3168 24.4589C26.4637 24.1842 26.5829 23.9121 26.6781 23.6082L21.9791 19.4419ZM20.3083 9H15.6917C13.4042 9 12.4876 9.177 11.5411 9.68321C10.7376 10.1129 10.1129 10.7376 9.68321 11.5411C9.177 12.4876 9 13.4042 9 15.6917V20.3083C9 22.5652 9.17229 23.4876 9.66305 24.4209L13.2929 20.7929C13.6586 20.4272 14.2429 20.4007 14.6402 20.7318L16.8612 22.5826L21.0831 17.3706C21.4417 16.9278 22.0972 16.8738 22.5236 17.2517L26.9887 21.2102C26.9964 20.9349 27 20.6356 27 20.3083V15.6917C27 13.4042 26.823 12.4876 26.3168 11.5411C25.8871 10.7376 25.2624 10.1129 24.4589 9.68321C23.5124 9.177 22.5958 9 20.3083 9ZM14.5 13C15.3284 13 16 13.6716 16 14.5C16 15.3284 15.3284 16 14.5 16C13.6716 16 13 15.3284 13 14.5C13 13.6716 13.6716 13 14.5 13Z" fill="#007AFF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -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;
@@ -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<Color>(
(Set<MaterialState> states) => bgColor ?? AppColor.colorTextButton),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(radius ?? 8),
side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton))),
padding: MaterialStateProperty.resolveWith<EdgeInsets>(
(Set<MaterialState> states) => const EdgeInsets.symmetric(horizontal: 16)),
elevation: MaterialStateProperty.resolveWith<double>(
(Set<MaterialState> states) => 0)),
child: Text(name,
textAlign: TextAlign.center,
style: textStyle ??
const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
);
}
@@ -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)));
}
}
@@ -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<FileUploader>()));
Get.lazyPut(() => ComposerDataSourceImpl(Get.find<DownloadClient>()));
Get.lazyPut(() => ContactDataSourceImpl());
Get.lazyPut(() => EmailDataSourceImpl(Get.find<EmailAPI>()));
Get.lazyPut(() => HtmlDataSourceImpl(
@@ -88,6 +92,7 @@ class ComposerBindings extends BaseBindings {
@override
void bindingsDataSource() {
Get.lazyPut<AttachmentUploadDataSource>(() => Get.find<AttachmentUploadDataSourceImpl>());
Get.lazyPut<ComposerDataSource>(() => Get.find<ComposerDataSourceImpl>());
Get.lazyPut<ContactDataSource>(() => Get.find<ContactDataSourceImpl>());
Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>());
Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>());
@@ -96,7 +101,9 @@ class ComposerBindings extends BaseBindings {
@override
void bindingsRepositoryImpl() {
Get.lazyPut(() => ComposerRepositoryImpl(Get.find<AttachmentUploadDataSource>()));
Get.lazyPut(() => ComposerRepositoryImpl(
Get.find<AttachmentUploadDataSource>(),
Get.find<ComposerDataSource>()));
Get.lazyPut(() => AutoCompleteRepositoryImpl(dataSources));
Get.lazyPut(() => ContactRepositoryImpl(Get.find<ContactDataSource>()));
Get.lazyPut(() => EmailRepositoryImpl(
@@ -132,6 +139,7 @@ class ComposerBindings extends BaseBindings {
Get.lazyPut(() => GetAllIdentitiesInteractor(Get.find<ManageAccountRepository>()));
Get.lazyPut(() => RemoveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => DownloadImageAsBase64Interactor(Get.find<ComposerRepository>()));
}
@override
@@ -153,6 +161,7 @@ class ComposerBindings extends BaseBindings {
Get.find<RemoveComposerCacheOnWebInteractor>(),
Get.find<SaveComposerCacheOnWebInteractor>(),
Get.find<RichTextWebController>(),
Get.find<DownloadImageAsBase64Interactor>(),
));
}
@@ -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<EmailAddress> listToEmailAddress = <EmailAddress>[];
List<EmailAddress> listCcEmailAddress = <EmailAddress>[];
@@ -104,6 +111,8 @@ class ComposerController extends BaseController {
List<Attachment> initialAttachments = <Attachment>[];
String? _textEditorWeb;
List<EmailContent>? _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 = <EmailBodyPart>{};
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();
@@ -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<ComposerController> 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<ComposerController> 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<ComposerController> 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<UploadFileState> uploadFilesState,
@@ -612,7 +595,6 @@ class ComposerView extends GetWidget<ComposerController> 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,
@@ -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<ComposerController>
: 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<ComposerController>
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<ComposerController>
children: [
_buildAttachmentsWidget(context),
_buildToolbarRichTextWidget(),
_buildInlineLoadingView(),
_buildEditorForm(context)
]
))),
@@ -483,7 +489,7 @@ class ComposerView extends GetWidget<ComposerController>
);
}
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<ComposerController>
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<ComposerController>
)
)
);
// });
}
Widget _buildAttachmentsWidget(BuildContext context) {
@@ -622,7 +638,6 @@ class ComposerView extends GetWidget<ComposerController>
context,
attachments,
controller.expandModeAttachments.value)),
_buildAttachmentsLoadingView(),
Padding(
padding: EdgeInsets.only(
bottom: 8,
@@ -639,19 +654,6 @@ class ComposerView extends GetWidget<ComposerController>
});
}
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<UploadFileState> uploadFilesState,
@@ -661,7 +663,6 @@ class ComposerView extends GetWidget<ComposerController>
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<ComposerController>
),
);
}
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();
}));
}
}
+43 -1
View File
@@ -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": {}
}
}
@@ -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');
}
}
+20 -2
View File
@@ -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
);
}
}
+2
View File
@@ -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
+3
View File
@@ -171,6 +171,9 @@ dependencies:
async: 2.8.2
# HTML5 parser
html: 0.15.0
dev_dependencies:
flutter_test:
sdk: flutter