From 81735363318db95cf841ac62c76ab9ac0626c9dd Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 18 Sep 2024 17:53:21 +0700 Subject: [PATCH] TF-2940 Add spell check for subject email --- contact/pubspec.lock | 17 +++++ .../extensions/color_extension.dart | 5 +- core/lib/presentation/utils/theme_utils.dart | 9 +++ .../views/text/text_field_builder.dart | 67 ++++++++++++++++--- core/pubspec.lock | 17 +++++ core/pubspec.yaml | 8 +++ .../presentation/composer_controller.dart | 6 +- .../presentation/styles/composer_style.dart | 6 +- .../widgets/subject_composer_widget.dart | 6 +- model/pubspec.lock | 17 +++++ pubspec.lock | 17 +++++ pubspec.yaml | 7 ++ 12 files changed, 164 insertions(+), 18 deletions(-) diff --git a/contact/pubspec.lock b/contact/pubspec.lock index beb8865df..ce6c98650 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -680,6 +680,15 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + languagetool_textfield: + dependency: transitive + description: + path: "." + ref: twake-supported + resolved-ref: f47dd9829e145acc795b4e3e62f8bc668135fcd6 + url: "https://github.com/dab246/languagetool_textfield.git" + source: git + version: "0.1.0" leak_tracker: dependency: transitive description: @@ -1076,6 +1085,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + throttling: + dependency: transitive + description: + name: throttling + sha256: e48a4c681b1838b8bf99c1a4f822efe43bb69132f9a56091cd5b7d931c862255 + url: "https://pub.dev" + source: hosted + version: "2.0.1" timing: dependency: transitive description: diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 4d859cdf1..85813ca8b 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -4,6 +4,7 @@ extension AppColor on Color { static const primaryColor = Color(0xFF007AFF); static const primaryDarkColor = Color(0xFF1C1C1C); static const primaryLightColor = Color(0xFFFFFFFF); + static const primarySelectedColor = Color(0xFFDFEEFF); static const baseTextColor = Color(0xFF7E869B); static const textFieldTextColor = Color(0xFF7E869B); static const textFieldLabelColor = Color(0xFF7E869B); @@ -16,11 +17,8 @@ extension AppColor on Color { static const loginTextFieldFocusedBorder = Color(0xFF007AFF); static const loginTextFieldHintColor = Color(0xff818C99); static const loginTextFieldBackgroundColor = Color(0xFFF2F3F5); - static const loginTextFieldBackgroundErrorColor = Color(0xFFFAEBEB); - static const buttonColor = Color(0xFF837DFF); static const appColor = Color(0xFF3840F7); static const nameUserColor = Color(0xFF182952); - static const emailUserColor = Color(0xFF7E869B); static const userInformationBackgroundColor = Color(0xFFF5F5F7); static const searchBorderColor = Color(0xFFEAEAEA); static const searchHintTextColor = Color(0xFF7E869B); @@ -29,7 +27,6 @@ extension AppColor on Color { static const mailboxSelectedTextColor = Color(0xFF3840F7); static const mailboxTextColor = Color(0xFF182952); static const mailboxSelectedTextNumberColor = Color(0xFF182952); - static const mailboxTextNumberColor = Color(0xFF837DFF); static const mailboxSelectedIconColor = Color(0xFF3840F7); static const mailboxIconColor = Color(0xFF7E869B); static const storageBackgroundColor = Color(0xFFF5F5F7); diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index ab5bf3722..d6bc7e4e4 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -11,6 +11,7 @@ class ThemeUtils { fontFamily: ConstantsUI.fontApp, appBarTheme: _appBarTheme, textTheme: _textTheme, + textSelectionTheme: _textSelectionTheme, dividerTheme: _dividerTheme, visualDensity: VisualDensity.adaptivePlatformDensity, scrollbarTheme: ScrollbarThemeData( @@ -27,6 +28,14 @@ class ThemeUtils { ); } + static TextSelectionThemeData get _textSelectionTheme { + return const TextSelectionThemeData( + cursorColor: AppColor.primaryColor, + selectionHandleColor: AppColor.primaryColor, + selectionColor: AppColor.primarySelectedColor, + ); + } + static AppBarTheme get _appBarTheme { return const AppBarTheme( color: Colors.white, diff --git a/core/lib/presentation/views/text/text_field_builder.dart b/core/lib/presentation/views/text/text_field_builder.dart index fbff332b4..3f162bc2d 100644 --- a/core/lib/presentation/views/text/text_field_builder.dart +++ b/core/lib/presentation/views/text/text_field_builder.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:flutter/material.dart'; +import 'package:languagetool_textfield/languagetool_textfield.dart'; class TextFieldBuilder extends StatefulWidget { @@ -10,7 +11,7 @@ class TextFieldBuilder extends StatefulWidget { final TapRegionCallback? onTapOutside; final TextStyle? textStyle; final TextInputAction? textInputAction; - final InputDecoration? decoration; + final InputDecoration decoration; final bool obscureText; final int? maxLines; final int? minLines; @@ -25,6 +26,7 @@ class TextFieldBuilder extends StatefulWidget { final TextDirection textDirection; final bool readOnly; final MouseCursor? mouseCursor; + final LanguageToolController? languageToolController; const TextFieldBuilder({ super.key, @@ -36,10 +38,11 @@ class TextFieldBuilder extends StatefulWidget { this.textStyle = const TextStyle(color: AppColor.textFieldTextColor), this.textDirection = TextDirection.ltr, this.textInputAction, - this.decoration, + this.decoration = const InputDecoration(), this.maxLines, this.minLines, this.controller, + this.languageToolController, this.keyboardType, this.focusNode, this.fromValue, @@ -57,22 +60,67 @@ class TextFieldBuilder extends StatefulWidget { class _TextFieldBuilderState extends State { - late TextEditingController _controller; + TextEditingController? _controller; + LanguageToolController? _languageToolController; + late TextDirection _textDirection; @override void initState() { super.initState(); - if (widget.fromValue != null) { - _controller = TextEditingController.fromValue(TextEditingValue(text: widget.fromValue!)); + if (widget.languageToolController != null) { + _languageToolController = widget.languageToolController; + if (widget.fromValue != null) { + _languageToolController?.value = TextEditingValue(text: widget.fromValue!); + } } else { - _controller = widget.controller ?? TextEditingController(); + if (widget.fromValue != null) { + _controller = TextEditingController.fromValue(TextEditingValue(text: widget.fromValue!)); + } else { + _controller = widget.controller ?? TextEditingController(); + } } _textDirection = widget.textDirection; } @override Widget build(BuildContext context) { + if (_languageToolController != null) { + return LanguageToolTextField( + key: widget.key, + controller: _languageToolController!, + cursorColor: widget.cursorColor, + autocorrect: widget.autocorrect, + textInputAction: widget.textInputAction, + decoration: widget.decoration, + maxLines: widget.maxLines, + minLines: widget.minLines, + keyboardAppearance: widget.keyboardAppearance, + style: widget.textStyle, + keyboardType: widget.keyboardType, + autoFocus: widget.autoFocus, + focusNode: widget.focusNode, + alignCenter: false, + textDirection: _textDirection, + readOnly: widget.readOnly, + mouseCursor: widget.mouseCursor, + onTextChange: (value) { + widget.onTextChange?.call(value); + if (value.isNotEmpty) { + final directionByText = DirectionUtils.getDirectionByEndsText(value); + if (directionByText != _textDirection) { + setState(() { + _textDirection = directionByText; + }); + } + } + }, + onTextSubmitted: widget.onTextSubmitted, + onTap: widget.onTap, + onTapOutside: widget.onTapOutside, + ); + } + return TextField( key: widget.key, controller: _controller, @@ -110,8 +158,11 @@ class _TextFieldBuilderState extends State { @override void dispose() { - if (widget.fromValue == null && widget.controller == null) { - _controller.dispose(); + if (widget.controller == null) { + _controller?.dispose(); + } + if (widget.languageToolController == null) { + _languageToolController?.dispose(); } super.dispose(); } diff --git a/core/pubspec.lock b/core/pubspec.lock index 00d679e4a..b520f7298 100644 --- a/core/pubspec.lock +++ b/core/pubspec.lock @@ -648,6 +648,15 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" + languagetool_textfield: + dependency: "direct main" + description: + path: "." + ref: twake-supported + resolved-ref: f47dd9829e145acc795b4e3e62f8bc668135fcd6 + url: "https://github.com/dab246/languagetool_textfield.git" + source: git + version: "0.1.0" leak_tracker: dependency: transitive description: @@ -1021,6 +1030,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + throttling: + dependency: transitive + description: + name: throttling + sha256: e48a4c681b1838b8bf99c1a4f822efe43bb69132f9a56091cd5b7d931c862255 + url: "https://pub.dev" + source: hosted + version: "2.0.1" timing: dependency: transitive description: diff --git a/core/pubspec.yaml b/core/pubspec.yaml index 0ebbc60a8..b376cd7b9 100644 --- a/core/pubspec.yaml +++ b/core/pubspec.yaml @@ -26,6 +26,14 @@ dependencies: flutter: sdk: flutter + ### Dependencies from git ### + # TODO: We will change it when the PR in upstream repository will be merged + # https://github.com/solid-software/languagetool_textfield/pull/83 + languagetool_textfield: + git: + url: https://github.com/dab246/languagetool_textfield.git + ref: twake-supported + ### Dependencies from pub.dev ### cupertino_icons: 1.0.6 diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 0deb2125e..a62ebfa68 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -24,6 +24,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:languagetool_textfield/languagetool_textfield.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -150,7 +151,9 @@ class ComposerController extends BaseController with DragDropFileMixin implement List listBccEmailAddress = []; ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.tMailContact; - final subjectEmailInputController = TextEditingController(); + final subjectEmailInputController = LanguageToolController( + delay: const Duration(milliseconds: 200), + ); final toEmailAddressController = TextEditingController(); final ccEmailAddressController = TextEditingController(); final bccEmailAddressController = TextEditingController(); @@ -1942,6 +1945,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement void handleOnFocusHtmlEditorWeb() { FocusManager.instance.primaryFocus?.unfocus(); + subjectEmailInputController.popupWidget?.popupRenderer.dismiss(); richTextWebController?.editorController.setFocus(); richTextWebController?.closeAllMenuPopup(); } diff --git a/lib/features/composer/presentation/styles/composer_style.dart b/lib/features/composer/presentation/styles/composer_style.dart index ca246c3c0..26bf5e3e1 100644 --- a/lib/features/composer/presentation/styles/composer_style.dart +++ b/lib/features/composer/presentation/styles/composer_style.dart @@ -19,17 +19,17 @@ class ComposerStyle { static const EdgeInsetsGeometry desktopRecipientPadding = EdgeInsetsDirectional.only(end: 24); static const EdgeInsetsGeometry desktopRecipientMargin = EdgeInsetsDirectional.only(start: 24); static const EdgeInsetsGeometry desktopSubjectMargin = EdgeInsetsDirectional.only(start: 24); - static const EdgeInsetsGeometry desktopSubjectPadding = EdgeInsetsDirectional.only(end: 24, top: 12, bottom: 12); + static const EdgeInsetsGeometry desktopSubjectPadding = EdgeInsetsDirectional.only(end: 24); static const EdgeInsetsGeometry desktopEditorPadding = EdgeInsetsDirectional.symmetric(horizontal: 20); static const EdgeInsetsGeometry tabletRecipientPadding = EdgeInsetsDirectional.only(end: 24); static const EdgeInsetsGeometry tabletRecipientMargin = EdgeInsetsDirectional.only(start: 24); static const EdgeInsetsGeometry tabletSubjectMargin = EdgeInsetsDirectional.only(start: 24); - static const EdgeInsetsGeometry tabletSubjectPadding = EdgeInsetsDirectional.only(end: 24, top: 12, bottom: 12); + static const EdgeInsetsGeometry tabletSubjectPadding = EdgeInsetsDirectional.only(end: 24); static const EdgeInsetsGeometry tabletEditorPadding = EdgeInsetsDirectional.symmetric(horizontal: 20); static const EdgeInsetsGeometry mobileRecipientPadding = EdgeInsetsDirectional.only(end: 16); static const EdgeInsetsGeometry mobileRecipientMargin = EdgeInsetsDirectional.only(start: 16); static const EdgeInsetsGeometry mobileSubjectMargin = EdgeInsetsDirectional.only(start: 16); - static const EdgeInsetsGeometry mobileSubjectPadding = EdgeInsetsDirectional.only(end: 16, top: 12, bottom: 12); + static const EdgeInsetsGeometry mobileSubjectPadding = EdgeInsetsDirectional.only(end: 16); static const EdgeInsetsGeometry mobileEditorPadding = EdgeInsetsDirectional.symmetric(horizontal: 12); static const EdgeInsetsGeometry popupItemPadding = EdgeInsetsDirectional.symmetric(horizontal: 12); static const EdgeInsetsGeometry insertImageLoadingBarPadding = EdgeInsetsDirectional.only(top: 12); diff --git a/lib/features/composer/presentation/widgets/subject_composer_widget.dart b/lib/features/composer/presentation/widgets/subject_composer_widget.dart index cfc4d8a7a..7dfc0e2bc 100644 --- a/lib/features/composer/presentation/widgets/subject_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/subject_composer_widget.dart @@ -1,13 +1,14 @@ import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:flutter/material.dart'; +import 'package:languagetool_textfield/languagetool_textfield.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/subject_composer_widget_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class SubjectComposerWidget extends StatelessWidget { final FocusNode? focusNode; - final TextEditingController textController; + final LanguageToolController textController; final ValueChanged? onTextChange; final EdgeInsetsGeometry? margin; final EdgeInsetsGeometry? padding; @@ -47,9 +48,10 @@ class SubjectComposerWidget extends StatelessWidget { focusNode: focusNode, onTextChange: onTextChange, maxLines: 1, + decoration: const InputDecoration(border: InputBorder.none), textDirection: DirectionUtils.getDirectionByLanguage(context), textStyle: SubjectComposerWidgetStyle.inputTextStyle, - controller: textController, + languageToolController: textController, ) ) ] diff --git a/model/pubspec.lock b/model/pubspec.lock index 37f9ab77c..d93d870fa 100644 --- a/model/pubspec.lock +++ b/model/pubspec.lock @@ -672,6 +672,15 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + languagetool_textfield: + dependency: transitive + description: + path: "." + ref: twake-supported + resolved-ref: f47dd9829e145acc795b4e3e62f8bc668135fcd6 + url: "https://github.com/dab246/languagetool_textfield.git" + source: git + version: "0.1.0" leak_tracker: dependency: transitive description: @@ -1053,6 +1062,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + throttling: + dependency: transitive + description: + name: throttling + sha256: e48a4c681b1838b8bf99c1a4f822efe43bb69132f9a56091cd5b7d931c862255 + url: "https://pub.dev" + source: hosted + version: "2.0.1" timing: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 7032bc747..801f9dc38 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1264,6 +1264,15 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + languagetool_textfield: + dependency: "direct main" + description: + path: "." + ref: twake-supported + resolved-ref: f47dd9829e145acc795b4e3e62f8bc668135fcd6 + url: "https://github.com/dab246/languagetool_textfield.git" + source: git + version: "0.1.0" leak_tracker: dependency: transitive description: @@ -1917,6 +1926,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + throttling: + dependency: transitive + description: + name: throttling + sha256: e48a4c681b1838b8bf99c1a4f822efe43bb69132f9a56091cd5b7d931c862255 + url: "https://pub.dev" + source: hosted + version: "2.0.1" timeago: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c9b1ef542..cc9fc0014 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -104,6 +104,13 @@ dependencies: url: https://github.com/linagora/flutter_pdf_render.git ref: main + # TODO: We will change it when the PR in upstream repository will be merged + # https://github.com/solid-software/languagetool_textfield/pull/83 + languagetool_textfield: + git: + url: https://github.com/dab246/languagetool_textfield.git + ref: twake-supported + ### Dependencies from pub.dev ### cupertino_icons: 1.0.6