TF-261 Add HtmlEditor for web browser

This commit is contained in:
dab246
2022-02-17 16:09:03 +07:00
committed by Dat H. Pham
parent 541cdb64f5
commit 2450cc9c6b
14 changed files with 136 additions and 68 deletions
+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12Z" fill="#007AFF"/>
<path d="M8.15912 14.1694C7.70202 15.406 7.41479 16.2748 7.29741 16.7759C6.92851 18.3507 6.66028 18.7054 8.03519 17.957C9.41011 17.2086 16.0663 13.5012 17.5513 12.6773C19.4871 11.6033 19.513 11.6872 17.4474 10.5469C15.8743 9.6784 9.29614 6.06777 8.03519 5.35883C6.77425 4.64989 6.92851 4.96514 7.29741 6.53991C7.41631 7.04748 7.70771 7.92406 8.17163 9.16964C8.4961 10.0408 9.25082 10.6801 10.1635 10.8569L14.0065 11.6013C14.0426 11.6085 14.066 11.6436 14.0588 11.6797C14.0535 11.7061 14.0329 11.7268 14.0065 11.732L10.1548 12.4757C9.2392 12.6525 8.48245 13.2947 8.15912 14.1694Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

+7 -2
View File
@@ -1,6 +1,7 @@
import 'dart:core';
import 'package:device_info/device_info.dart';
import 'package:device_info_plus/device_info_plus.dart';
class DeviceManager {
final DeviceInfoPlugin _deviceInfoPlugin;
@@ -9,6 +10,10 @@ class DeviceManager {
Future<bool> isNeedRequestStoragePermissionOnAndroid() async {
final androidInfo = await _deviceInfoPlugin.androidInfo;
return androidInfo.version.sdkInt <= 28;
final sdkInt = androidInfo.version.sdkInt;
if (sdkInt != null) {
return sdkInt <= 28;
}
return false;
}
}
@@ -79,6 +79,7 @@ class ImagePaths {
String get icFilterMessageAll => _getImagePath('ic_filter_message_all.svg');
String get icFilterMessageAttachments => _getImagePath('ic_filter_message_attachments.svg');
String get icLogoTMailV2 => _getImagePath('logo_tmail_v2.png');
String get icSendToast => _getImagePath('ic_send_toast.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
+4 -3
View File
@@ -61,7 +61,8 @@ class AppToast {
}
void showToastWithIcon(BuildContext context,
{String? message, String? icon, Color? bgColor, double? radius, EdgeInsets? padding, TextStyle? textStyle}) {
{String? message, String? icon, Color? bgColor,
Color? textColor, double? radius, EdgeInsets? padding, TextStyle? textStyle}) {
final toast = Material(
color: bgColor ?? Colors.white,
elevation: 10,
@@ -71,7 +72,7 @@ class AppToast {
padding: padding ?? EdgeInsets.symmetric(horizontal: 12.0, vertical: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius ?? 10.0),
color: Colors.white,
color: bgColor ?? Colors.white,
),
width: 320,
child: Row(
@@ -81,7 +82,7 @@ class AppToast {
SizedBox(width: 10.0),
Expanded(child: Text(
message ?? '',
style: textStyle ?? TextStyle(fontSize: 15, color: Colors.black))),
style: textStyle ?? TextStyle(fontSize: 15, color: textColor ?? Colors.black))),
],
),
),
+2 -2
View File
@@ -55,8 +55,8 @@ dependencies:
# GetX
get: 4.1.4
# device_info
device_info: 2.0.2
# device_info_plus
device_info_plus: 3.2.2
# getwidget
getwidget: 2.0.4
@@ -1,4 +1,5 @@
import 'package:core/core.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/composer/data/datasource/autocomplete_datasource.dart';
@@ -70,7 +71,9 @@ class ComposerBindings extends Bindings {
Get.find<GetAutoCompleteInteractor>(),
Get.find<GetAutoCompleteWithDeviceContactInteractor>(),
Get.find<AppToast>(),
Get.find<ImagePaths>(),
Get.find<Uuid>(),
Get.find<DeviceInfoPlugin>(),
Get.find<TextEditingController>(),
Get.find<LocalFilePickerInteractor>(),
Get.find<UploadMultipleAttachmentInteractor>()));
@@ -3,13 +3,15 @@ import 'dart:async';
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:enough_html_editor/enough_html_editor.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_chips_input/flutter_chips_input.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart' as HtmlEditorBrowser;
import 'package:http_parser/http_parser.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
@@ -39,7 +41,6 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/utils/app_logger.dart';
import 'package:uuid/uuid.dart';
import 'dart:developer' as developer;
class ComposerController extends BaseController {
@@ -55,10 +56,12 @@ class ComposerController extends BaseController {
final GetAutoCompleteInteractor _getAutoCompleteInteractor;
final GetAutoCompleteWithDeviceContactInteractor _getAutoCompleteWithDeviceContactInteractor;
final AppToast _appToast;
final ImagePaths _imagePaths;
final Uuid _uuid;
final TextEditingController subjectEmailInputController;
final LocalFilePickerInteractor _localFilePickerInteractor;
final UploadMultipleAttachmentInteractor _uploadMultipleAttachmentInteractor;
final DeviceInfoPlugin _deviceInfoPlugin;
List<EmailAddress> listToEmailAddress = [];
List<EmailAddress> listCcEmailAddress = [];
@@ -66,19 +69,30 @@ class ComposerController extends BaseController {
String? _subjectEmail;
ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.localContact;
HtmlEditorApi? htmlEditorApi;
final HtmlEditorBrowser.HtmlEditorController htmlControllerBrowser = HtmlEditorBrowser.HtmlEditorController();
final keyToEmailAddress = GlobalKey<ChipsInputState>();
final keyCcEmailAddress = GlobalKey<ChipsInputState>();
final keyBccEmailAddress = GlobalKey<ChipsInputState>();
void setSubjectEmail(String subject) => _subjectEmail = subject;
Future<String> _getEmailBodyText() async {
if (kIsWeb) {
return await htmlControllerBrowser.getText();
} else {
return (await htmlEditorApi?.getFullHtml()) ?? '';
}
}
ComposerController(
this._sendEmailInteractor,
this._saveEmailAddressInteractor,
this._getAutoCompleteInteractor,
this._getAutoCompleteWithDeviceContactInteractor,
this._appToast,
this._imagePaths,
this._uuid,
this._deviceInfoPlugin,
this.subjectEmailInputController,
this._localFilePickerInteractor,
this._uploadMultipleAttachmentInteractor,
@@ -124,7 +138,13 @@ class ComposerController extends BaseController {
@override
void onError(error) {
_appToast.showErrorToast(AppLocalizations.of(Get.context!).error_message_sent);
if (Get.overlayContext != null && Get.context != null) {
_appToast.showToastWithIcon(
Get.overlayContext!,
textColor: AppColor.toastErrorBackgroundColor,
message: AppLocalizations.of(Get.context!).message_has_been_sent_failure,
icon: _imagePaths.icSendToast);
}
popBack();
}
@@ -261,7 +281,8 @@ class ComposerController extends BaseController {
final generatePartId = PartId(_uuid.v1());
final generateBlobId = Id(_uuid.v1());
final emailBodyText = (await htmlEditorApi?.getFullHtml()) ?? '';
final emailBodyText = await _getEmailBodyText();
final userAgent = await userAgentPlatform;
return Email(
generateEmailId,
@@ -280,17 +301,22 @@ class ComposerController extends BaseController {
bodyValues: {
generatePartId: EmailBodyValue(emailBodyText, false, false)
},
headerUserAgent: {IndividualHeaderIdentifier.headerUserAgent : userAgentPlatform},
headerUserAgent: {IndividualHeaderIdentifier.headerUserAgent : userAgent},
attachments: attachments.isNotEmpty ? _generateAttachments() : null,
);
}
String get userAgentPlatform {
Future<String> get userAgentPlatform async {
String userAgent;
try {
userAgent = FkUserAgent.userAgent ?? '';
if (kIsWeb) {
final webBrowserInfo = await _deviceInfoPlugin.webBrowserInfo;
userAgent = webBrowserInfo.userAgent ?? '';
} else {
userAgent = FkUserAgent.userAgent ?? '';
}
log('ComposerController - userAgentPlatform(): userAgent: $userAgent');
} on PlatformException {
} on Exception {
userAgent = '';
}
return userAgent;
@@ -1,6 +1,8 @@
import 'package:core/core.dart';
import 'package:enough_html_editor/enough_html_editor.dart';
import 'package:html_editor_enhanced/html_editor.dart' as HtmlEditorBrowser;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
@@ -22,8 +24,7 @@ class ComposerView extends GetWidget<ComposerController> {
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
controller.htmlEditorApi?.unfocus();
controller.htmlEditorApi?.unfocus(context);
},
child: Scaffold(
backgroundColor: AppColor.primaryLightColor,
@@ -225,28 +226,36 @@ class ComposerView extends GetWidget<ComposerController> {
Widget _buildComposerEditor(BuildContext context) {
return Obx(() {
if (controller.composerArguments.value?.emailActionType == EmailActionType.compose) {
return HtmlEditor(
if (kIsWeb) {
return HtmlEditorBrowser.HtmlEditor(
key: Key('composer_editor'),
minHeight: 100,
supportZoom: true,
disableHorizontalScroll: false,
disableVerticalScroll: false,
onCreated: (editorApi) => controller.htmlEditorApi = editorApi,
controller: controller.htmlControllerBrowser,
htmlEditorOptions: HtmlEditorBrowser.HtmlEditorOptions(
shouldEnsureVisible: true,
initialText: controller.getContentEmail(),
autoAdjustHeight: controller.getContentEmail().isNotEmpty,
),
htmlToolbarOptions: HtmlEditorBrowser.HtmlToolbarOptions(
toolbarPosition: HtmlEditorBrowser.ToolbarPosition.custom
),
otherOptions: HtmlEditorBrowser.OtherOptions(height: 550),
);
} else {
final message = controller.getContentEmail();
return message.isNotEmpty
? HtmlEditor(
if (controller.composerArguments.value?.emailActionType == EmailActionType.compose) {
return HtmlEditor(
key: Key('composer_editor'),
minHeight: 100,
supportZoom: true,
disableHorizontalScroll: false,
disableVerticalScroll: false,
onCreated: (editorApi) => controller.htmlEditorApi = editorApi,
initialContent: message,
)
: SizedBox.shrink();
onCreated: (editorApi) => controller.htmlEditorApi = editorApi);
} else {
final message = controller.getContentEmail();
return message.isNotEmpty
? HtmlEditor(
key: Key('composer_editor'),
minHeight: 100,
onCreated: (editorApi) => controller.htmlEditorApi = editorApi,
initialContent: message)
: SizedBox.shrink();
}
}
});
}
@@ -61,13 +61,13 @@ class EmailView extends GetView {
Widget _buildBottomBar(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 6, top: 6, right: 6, bottom: 6),
child: (BottomBarMailWidgetBuilder(
child: Obx(() => (BottomBarMailWidgetBuilder(
context,
imagePaths,
responsiveUtils,
emailController.mailboxDashBoardController.selectedEmail.value)
..addOnPressEmailAction((emailActionType) => emailController.pressEmailAction(emailActionType)))
.build());
.build()));
}
Widget _buildEmailBody(BuildContext context) {
@@ -16,11 +16,12 @@ class MailboxDashBoardBindings extends Bindings {
Get.lazyPut(() => CredentialRepositoryImpl(Get.find<SharedPreferences>()));
Get.lazyPut<CredentialRepository>(() => Get.find<CredentialRepositoryImpl>());
Get.lazyPut(() => GetUserProfileInteractor(Get.find<CredentialRepository>()));
Get.lazyPut(() => AppToast());
Get.put(MailboxDashBoardController(
Get.find<GetUserProfileInteractor>(),
Get.find<AppToast>()));
Get.find<AppToast>(),
Get.find<ImagePaths>(),
));
MailboxBindings().dependencies();
ThreadBindings().dependencies();
@@ -21,6 +21,7 @@ class MailboxDashBoardController extends BaseController {
final GetUserProfileInteractor _getUserProfileInteractor;
final AppToast _appToast;
final ImagePaths _imagePaths;
final scaffoldKey = GlobalKey<ScaffoldState>();
final selectedMailbox = Rxn<PresentationMailbox>();
@@ -37,7 +38,11 @@ class MailboxDashBoardController extends BaseController {
Map<Role, MailboxId> mapDefaultMailboxId = Map();
Map<MailboxId, PresentationMailbox> mapMailbox = Map();
MailboxDashBoardController(this._getUserProfileInteractor, this._appToast);
MailboxDashBoardController(
this._getUserProfileInteractor,
this._appToast,
this._imagePaths,
);
@override
void onReady() {
@@ -51,8 +56,11 @@ class MailboxDashBoardController extends BaseController {
super.onData(newState);
viewState.value.map((success) {
if (success is SendingEmailState) {
if (Get.context != null) {
_appToast.showToast(AppLocalizations.of(Get.context!).your_email_being_sent);
if (Get.overlayContext != null && Get.context != null) {
_appToast.showToastWithIcon(
Get.overlayContext!,
message: AppLocalizations.of(Get.context!).your_email_being_sent,
icon: _imagePaths.icSendToast);
}
}
});
@@ -63,20 +71,28 @@ class MailboxDashBoardController extends BaseController {
viewState.value.fold(
(failure) {
if (failure is SendEmailFailure) {
if (Get.context != null) {
_appToast.showErrorToast(AppLocalizations.of(Get.context!).error_message_sent);
clearState();
if (Get.overlayContext != null && Get.context != null) {
_appToast.showToastWithIcon(
Get.overlayContext!,
textColor: AppColor.toastErrorBackgroundColor,
message: AppLocalizations.of(Get.context!).message_has_been_sent_failure,
icon: _imagePaths.icSendToast);
}
clearState();
}
},
(success) {
if (success is GetUserProfileSuccess) {
userProfile.value = success.userProfile;
} else if (success is SendEmailSuccess) {
if (Get.context != null) {
_appToast.showSuccessToast(AppLocalizations.of(Get.context!).message_sent);
clearState();
if (Get.overlayContext != null && Get.context != null) {
_appToast.showToastWithIcon(
Get.overlayContext!,
textColor: AppColor.toastSuccessBackgroundColor,
message: AppLocalizations.of(Get.context!).message_has_been_sent_successfully,
icon: _imagePaths.icSendToast);
}
clearState();
}
}
);
+1 -1
View File
@@ -1,5 +1,5 @@
import 'package:core/core.dart';
import 'package:device_info/device_info.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
+14 -14
View File
@@ -214,20 +214,6 @@ class AppLocalizations {
);
}
String get message_sent {
return Intl.message(
'Message sent',
name: 'message_sent',
);
}
String get error_message_sent {
return Intl.message(
'Error message sent',
name: 'error_message_sent',
);
}
String count_email_selected(int count) {
return Intl.message(
'$count selected',
@@ -585,4 +571,18 @@ class AppLocalizations {
'With Starred',
name: 'with_starred');
}
String get message_has_been_sent_successfully {
return Intl.message(
'Message has been sent successfully',
name: 'message_has_been_sent_successfully',
);
}
String get message_has_been_sent_failure {
return Intl.message(
'Message has been sent failure',
name: 'message_has_been_sent_failure',
);
}
}
+8 -6
View File
@@ -95,8 +95,8 @@ dependencies:
# path_provider
path_provider: 2.0.3
# device_info
device_info: 2.0.2
# device_info_plus
device_info_plus: 3.2.2
# permission_handler
permission_handler: 8.1.6
@@ -111,16 +111,18 @@ dependencies:
ref: master
# file_picker
file_picker: 3.0.2+2
file_picker: 4.2.0
# hive
hive: 2.0.4
# enough_html_editor
# enough_html_editor: Compose email for Mobile & Tablet
enough_html_editor:
git:
url: https://github.com/dab246/enough_html_editor.git
ref: fix_bug_enough_platform_widgets
url: https://github.com/Enough-Software/enough_html_editor.git
# html_editor_enhanced: Compose email for Web Browser
html_editor_enhanced: 2.4.0+1
# fk_user_agent
fk_user_agent: 2.1.0