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
@@ -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();
}
}
);