From 2450cc9c6b0f3c89bf57573847d13f5184da3c9c Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 17 Feb 2022 16:09:03 +0700 Subject: [PATCH] TF-261 Add HtmlEditor for web browser --- assets/images/ic_send_toast.svg | 4 ++ core/lib/data/utils/device_manager.dart | 9 +++- .../presentation/resources/image_paths.dart | 1 + core/lib/presentation/utils/app_toast.dart | 7 +-- core/pubspec.yaml | 4 +- .../presentation/composer_bindings.dart | 3 ++ .../presentation/composer_controller.dart | 42 +++++++++++++---- .../composer/presentation/composer_view.dart | 47 +++++++++++-------- .../email/presentation/email_view.dart | 4 +- .../mailbox_dashboard_bindings.dart | 5 +- .../mailbox_dashboard_controller.dart | 34 ++++++++++---- lib/main/bindings/core/core_bindings.dart | 2 +- lib/main/localizations/app_localizations.dart | 28 +++++------ pubspec.yaml | 14 +++--- 14 files changed, 136 insertions(+), 68 deletions(-) create mode 100644 assets/images/ic_send_toast.svg diff --git a/assets/images/ic_send_toast.svg b/assets/images/ic_send_toast.svg new file mode 100644 index 000000000..34a878c9c --- /dev/null +++ b/assets/images/ic_send_toast.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/lib/data/utils/device_manager.dart b/core/lib/data/utils/device_manager.dart index bfc666408..5462b20fa 100644 --- a/core/lib/data/utils/device_manager.dart +++ b/core/lib/data/utils/device_manager.dart @@ -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 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; } } \ No newline at end of file diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 289306ae1..392313734 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -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; diff --git a/core/lib/presentation/utils/app_toast.dart b/core/lib/presentation/utils/app_toast.dart index 5a2c2e8df..6365d0463 100644 --- a/core/lib/presentation/utils/app_toast.dart +++ b/core/lib/presentation/utils/app_toast.dart @@ -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))), ], ), ), diff --git a/core/pubspec.yaml b/core/pubspec.yaml index ddb5c6eea..8cf69db04 100644 --- a/core/pubspec.yaml +++ b/core/pubspec.yaml @@ -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 diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index da75a9993..bd872bf03 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -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(), Get.find(), Get.find(), + Get.find(), Get.find(), + 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 5e2311a47..0d58427b6 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -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 listToEmailAddress = []; List 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(); final keyCcEmailAddress = GlobalKey(); final keyBccEmailAddress = GlobalKey(); void setSubjectEmail(String subject) => _subjectEmail = subject; + Future _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 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; diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 532625108..889030ce3 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -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 { 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 { 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(); + } } }); } diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index a5f707aa8..8b48cc546 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -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) { diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_bindings.dart index 47865d54a..fbf0843db 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_bindings.dart @@ -16,11 +16,12 @@ class MailboxDashBoardBindings extends Bindings { Get.lazyPut(() => CredentialRepositoryImpl(Get.find())); Get.lazyPut(() => Get.find()); Get.lazyPut(() => GetUserProfileInteractor(Get.find())); - Get.lazyPut(() => AppToast()); Get.put(MailboxDashBoardController( Get.find(), - Get.find())); + Get.find(), + Get.find(), + )); MailboxBindings().dependencies(); ThreadBindings().dependencies(); diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart index a7db55ec6..77685321b 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart @@ -21,6 +21,7 @@ class MailboxDashBoardController extends BaseController { final GetUserProfileInteractor _getUserProfileInteractor; final AppToast _appToast; + final ImagePaths _imagePaths; final scaffoldKey = GlobalKey(); final selectedMailbox = Rxn(); @@ -37,7 +38,11 @@ class MailboxDashBoardController extends BaseController { Map mapDefaultMailboxId = Map(); Map 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(); } } ); diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index f569cdd71..0712989ca 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -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'; diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index f71d6b1b9..12983df9b 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -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', + ); + } } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index d4bd6816f..b3ea7d9b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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