From 6bb0e55c9f2500c247cc4eb91afe2edf2a12015e Mon Sep 17 00:00:00 2001 From: Gleb Date: Mon, 24 Apr 2023 19:31:55 +0300 Subject: [PATCH 1/7] Create initial design for TextField --- example/lib/app.dart | 23 ++++++- example/pubspec.lock | 4 +- example/pubspec.yaml | 5 +- lib/languagetool_textfield.dart | 4 ++ .../language_tool_text_field.dart | 55 +++++++++++++++- .../widgets/custom_text_field_controller.dart | 66 +++++++++++++++++++ 6 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 lib/presentation/widgets/custom_text_field_controller.dart diff --git a/example/lib/app.dart b/example/lib/app.dart index f3d9ca4..208ee9b 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:language_tool/language_tool.dart'; +import 'package:languagetool_textfield/languagetool_textfield.dart'; +/// Main screen widget demonstrating library usage example class App extends StatefulWidget { + /// Creates a new instance of main screen widget const App({super.key}); @override @@ -8,8 +12,25 @@ class App extends StatefulWidget { } class _AppState extends State { + final LangToolService _langToolService = LangToolService(LanguageTool()); + + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { - return const Placeholder(); + return Scaffold( + body: Center( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: LanguageToolTextField( + langService: _langToolService, + style: const TextStyle(), + ), + ), + ), + ); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 6cae1f7..6aba7bb 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -196,7 +196,7 @@ packages: source: hosted version: "4.8.0" language_tool: - dependency: transitive + dependency: "direct main" description: name: language_tool sha256: "90ceb6f0a0b57fb3a5b88be82ffd676c90639cd06d622d25f76add30d5a2acd6" @@ -204,7 +204,7 @@ packages: source: hosted version: "2.1.1" languagetool_textfield: - dependency: "direct dev" + dependency: "direct main" description: path: ".." relative: true diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 75fe569..f0491c3 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -9,12 +9,13 @@ environment: dependencies: flutter: sdk: flutter + language_tool: ^2.1.1 + languagetool_textfield: + path: ../ dev_dependencies: flutter_test: sdk: flutter - languagetool_textfield: - path: ../ solid_lints: ^0.0.14 flutter: diff --git a/lib/languagetool_textfield.dart b/lib/languagetool_textfield.dart index abe7c4c..0dfd238 100644 --- a/lib/languagetool_textfield.dart +++ b/lib/languagetool_textfield.dart @@ -1 +1,5 @@ library languagetool_textfield; + +export 'domain/language_check_service.dart'; +export 'implementations/lang_tool_service.dart'; +export 'presentation/language_tool_text_field.dart'; diff --git a/lib/presentation/language_tool_text_field.dart b/lib/presentation/language_tool_text_field.dart index 769b5ce..15fc313 100644 --- a/lib/presentation/language_tool_text_field.dart +++ b/lib/presentation/language_tool_text_field.dart @@ -1,17 +1,19 @@ import 'package:flutter/material.dart'; import 'package:languagetool_textfield/domain/language_check_service.dart'; +import 'package:languagetool_textfield/domain/mistake.dart'; +import 'package:languagetool_textfield/presentation/widgets/custom_text_field_controller.dart'; class LanguageToolTextField extends StatefulWidget { final LanguageCheckService langService; final TextStyle style; - final InputDecoration decoration; + final InputDecoration? decoration; final Widget Function()? mistakeBuilder; const LanguageToolTextField({ Key? key, required this.langService, required this.style, - required this.decoration, + this.decoration, this.mistakeBuilder, }) : super(key: key); @@ -20,9 +22,56 @@ class LanguageToolTextField extends StatefulWidget { } class _LanguageToolTextFieldState extends State { + final _textFieldController = CustomTextFieldController( + text: 'OkayOkayOkay', + mistakes: [ + const Mistake( + message: 'bad', + type: 'bad', + offset: 1, + length: 2, + replacements: ['1'], + ), + const Mistake( + message: 'bad', + type: 'bad', + offset: 4, + length: 2, + replacements: ['1'], + ), + const Mistake( + message: 'bad', + type: 'bad', + offset: 8, + length: 2, + replacements: ['1'], + ), + ], + ); + static const _borderRadius = 15.0; + static const _borderOpacity = 0.5; + final _textFieldBorder = OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey.withOpacity(_borderOpacity), + ), + borderRadius: BorderRadius.circular(_borderRadius), + ); @override Widget build(BuildContext context) { - return const Placeholder(); + return TextField( + autocorrect: false, + enableSuggestions: false, + textCapitalization: TextCapitalization.none, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: widget.decoration ?? + InputDecoration( + focusedBorder: _textFieldBorder, + enabledBorder: _textFieldBorder, + border: _textFieldBorder, + ), + controller: _textFieldController, + ); } } diff --git a/lib/presentation/widgets/custom_text_field_controller.dart b/lib/presentation/widgets/custom_text_field_controller.dart new file mode 100644 index 0000000..0292f47 --- /dev/null +++ b/lib/presentation/widgets/custom_text_field_controller.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:languagetool_textfield/domain/mistake.dart'; + +/// Custom controller for an editable text field, that supports +/// mistakes highlighting. +class CustomTextFieldController extends TextEditingController { + /// List of mistakes in text. + final List mistakes; + + /// Creates a controller for an editable text field. + CustomTextFieldController({ + String? text, + this.mistakes = const [], + }) : super(text: text); + + @override + TextSpan buildTextSpan({ + required BuildContext context, + TextStyle? style, + required bool withComposing, + }) { + final List children = []; + const underlineThickness = 2.0; + const backgroundOpacity = 0.2; + + if (mistakes.isEmpty) { + return TextSpan(text: text, style: style); + } + + for (int i = 0; i < mistakes.length; i++) { + final mistake = mistakes[i]; + final previousMistakePosition = + i > 0 ? mistakes[i - 1].offset + mistakes[i - 1].length : 0; + final mistakeStart = mistake.offset; + final mistakeEnd = mistakeStart + mistake.length; + + children.add( + TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), + ); + + children.add( + TextSpan( + text: text.substring(mistakeStart, mistakeEnd), + style: TextStyle( + decoration: TextDecoration.underline, + decorationColor: Colors.red, + decorationThickness: underlineThickness, + backgroundColor: Colors.red.withOpacity(backgroundOpacity), + ), + ), + ); + + if (mistake == mistakes.last) { + children.add( + TextSpan( + text: text.substring( + mistakeEnd, + ), + ), + ); + } + } + + return TextSpan(children: children, style: style); + } +} From 118c1ec5b44a286cc21ee4cddb16c32a49225388 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 25 Apr 2023 12:46:03 +0300 Subject: [PATCH 2/7] Fixes after review --- example/lib/app.dart | 4 ++-- .../language_tool_text_field.dart | 11 +++++---- ...anguage_tool_text_editing_controller.dart} | 23 +++++++++++-------- 3 files changed, 23 insertions(+), 15 deletions(-) rename lib/presentation/widgets/{custom_text_field_controller.dart => language_tool_text_editing_controller.dart} (71%) diff --git a/example/lib/app.dart b/example/lib/app.dart index 208ee9b..6d1ee71 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:language_tool/language_tool.dart'; import 'package:languagetool_textfield/languagetool_textfield.dart'; -/// Main screen widget demonstrating library usage example +/// A main screen widget demonstrating library usage example class App extends StatefulWidget { /// Creates a new instance of main screen widget const App({super.key}); @@ -12,7 +12,7 @@ class App extends StatefulWidget { } class _AppState extends State { - final LangToolService _langToolService = LangToolService(LanguageTool()); + final _langToolService = LangToolService(LanguageTool()); @override void initState() { diff --git a/lib/presentation/language_tool_text_field.dart b/lib/presentation/language_tool_text_field.dart index 15fc313..b6245a6 100644 --- a/lib/presentation/language_tool_text_field.dart +++ b/lib/presentation/language_tool_text_field.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:languagetool_textfield/domain/language_check_service.dart'; import 'package:languagetool_textfield/domain/mistake.dart'; -import 'package:languagetool_textfield/presentation/widgets/custom_text_field_controller.dart'; +import 'package:languagetool_textfield/presentation/widgets/language_tool_text_editing_controller.dart'; class LanguageToolTextField extends StatefulWidget { final LanguageCheckService langService; - final TextStyle style; + final TextStyle? style; final InputDecoration? decoration; final Widget Function()? mistakeBuilder; + final LanguageToolTextEditingController? controller; const LanguageToolTextField({ Key? key, @@ -15,6 +16,7 @@ class LanguageToolTextField extends StatefulWidget { required this.style, this.decoration, this.mistakeBuilder, + this.controller, }) : super(key: key); @override @@ -22,7 +24,7 @@ class LanguageToolTextField extends StatefulWidget { } class _LanguageToolTextFieldState extends State { - final _textFieldController = CustomTextFieldController( + final _textFieldController = LanguageToolTextEditingController( text: 'OkayOkayOkay', mistakes: [ const Mistake( @@ -65,13 +67,14 @@ class _LanguageToolTextFieldState extends State { textCapitalization: TextCapitalization.none, keyboardType: TextInputType.multiline, maxLines: null, + style: widget.style, decoration: widget.decoration ?? InputDecoration( focusedBorder: _textFieldBorder, enabledBorder: _textFieldBorder, border: _textFieldBorder, ), - controller: _textFieldController, + controller: widget.controller ?? _textFieldController, ); } } diff --git a/lib/presentation/widgets/custom_text_field_controller.dart b/lib/presentation/widgets/language_tool_text_editing_controller.dart similarity index 71% rename from lib/presentation/widgets/custom_text_field_controller.dart rename to lib/presentation/widgets/language_tool_text_editing_controller.dart index 0292f47..6c25a3e 100644 --- a/lib/presentation/widgets/custom_text_field_controller.dart +++ b/lib/presentation/widgets/language_tool_text_editing_controller.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:languagetool_textfield/domain/mistake.dart'; -/// Custom controller for an editable text field, that supports +/// A custom controller for an editable text field, that supports /// mistakes highlighting. -class CustomTextFieldController extends TextEditingController { - /// List of mistakes in text. +class LanguageToolTextEditingController extends TextEditingController { + /// A list of mistakes in the text. final List mistakes; /// Creates a controller for an editable text field. - CustomTextFieldController({ + LanguageToolTextEditingController({ String? text, this.mistakes = const [], }) : super(text: text); @@ -19,7 +19,7 @@ class CustomTextFieldController extends TextEditingController { TextStyle? style, required bool withComposing, }) { - final List children = []; + final children = []; const underlineThickness = 2.0; const backgroundOpacity = 0.2; @@ -29,8 +29,12 @@ class CustomTextFieldController extends TextEditingController { for (int i = 0; i < mistakes.length; i++) { final mistake = mistakes[i]; - final previousMistakePosition = - i > 0 ? mistakes[i - 1].offset + mistakes[i - 1].length : 0; + int previousMistakePosition = 0; + if (i > 0) { + final previousMistake = mistakes[i - 1]; + previousMistakePosition = + previousMistake.offset + previousMistake.length; + } final mistakeStart = mistake.offset; final mistakeEnd = mistakeStart + mistake.length; @@ -38,10 +42,11 @@ class CustomTextFieldController extends TextEditingController { TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), ); + final textStyle = style ?? const TextStyle(); children.add( TextSpan( text: text.substring(mistakeStart, mistakeEnd), - style: TextStyle( + style: textStyle.copyWith( decoration: TextDecoration.underline, decorationColor: Colors.red, decorationThickness: underlineThickness, @@ -50,7 +55,7 @@ class CustomTextFieldController extends TextEditingController { ), ); - if (mistake == mistakes.last) { + if (i == mistakes.length - 1) { children.add( TextSpan( text: text.substring( From 2b9be93070fcf8523ab1f10c77fb304c3aaf9417 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 25 Apr 2023 17:19:12 +0300 Subject: [PATCH 3/7] Fix after review --- example/lib/app.dart | 29 +++++++-- lib/languagetool_textfield.dart | 1 + .../language_tool_text_field.dart | 29 +-------- ...language_tool_text_editing_controller.dart | 62 ++++++++++--------- 4 files changed, 61 insertions(+), 60 deletions(-) diff --git a/example/lib/app.dart b/example/lib/app.dart index 6d1ee71..e42dfd9 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:language_tool/language_tool.dart'; +import 'package:languagetool_textfield/domain/mistake.dart'; import 'package:languagetool_textfield/languagetool_textfield.dart'; /// A main screen widget demonstrating library usage example @@ -14,11 +15,6 @@ class App extends StatefulWidget { class _AppState extends State { final _langToolService = LangToolService(LanguageTool()); - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -27,6 +23,29 @@ class _AppState extends State { padding: const EdgeInsets.all(20.0), child: LanguageToolTextField( langService: _langToolService, + controller: LanguageToolTextEditingController( + text: 'OKAYOKAYOKAY', + mistakes: [ + const Mistake( + message: 'bad', + type: 'bad', + offset: 2, + length: 2, + ), + const Mistake( + message: 'bad', + type: 'bad', + offset: 5, + length: 1, + ), + const Mistake( + message: 'bad', + type: 'bad', + offset: 7, + length: 3, + ), + ], + ), style: const TextStyle(), ), ), diff --git a/lib/languagetool_textfield.dart b/lib/languagetool_textfield.dart index 0dfd238..7d83b3c 100644 --- a/lib/languagetool_textfield.dart +++ b/lib/languagetool_textfield.dart @@ -3,3 +3,4 @@ library languagetool_textfield; export 'domain/language_check_service.dart'; export 'implementations/lang_tool_service.dart'; export 'presentation/language_tool_text_field.dart'; +export 'presentation/widgets/language_tool_text_editing_controller.dart'; diff --git a/lib/presentation/language_tool_text_field.dart b/lib/presentation/language_tool_text_field.dart index b6245a6..f0e17fa 100644 --- a/lib/presentation/language_tool_text_field.dart +++ b/lib/presentation/language_tool_text_field.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:languagetool_textfield/domain/language_check_service.dart'; -import 'package:languagetool_textfield/domain/mistake.dart'; import 'package:languagetool_textfield/presentation/widgets/language_tool_text_editing_controller.dart'; class LanguageToolTextField extends StatefulWidget { @@ -24,34 +23,10 @@ class LanguageToolTextField extends StatefulWidget { } class _LanguageToolTextFieldState extends State { - final _textFieldController = LanguageToolTextEditingController( - text: 'OkayOkayOkay', - mistakes: [ - const Mistake( - message: 'bad', - type: 'bad', - offset: 1, - length: 2, - replacements: ['1'], - ), - const Mistake( - message: 'bad', - type: 'bad', - offset: 4, - length: 2, - replacements: ['1'], - ), - const Mistake( - message: 'bad', - type: 'bad', - offset: 8, - length: 2, - replacements: ['1'], - ), - ], - ); static const _borderRadius = 15.0; static const _borderOpacity = 0.5; + + final _textFieldController = LanguageToolTextEditingController(); final _textFieldBorder = OutlineInputBorder( borderSide: BorderSide( color: Colors.grey.withOpacity(_borderOpacity), diff --git a/lib/presentation/widgets/language_tool_text_editing_controller.dart b/lib/presentation/widgets/language_tool_text_editing_controller.dart index 6c25a3e..dc88ec9 100644 --- a/lib/presentation/widgets/language_tool_text_editing_controller.dart +++ b/lib/presentation/widgets/language_tool_text_editing_controller.dart @@ -27,43 +27,49 @@ class LanguageToolTextEditingController extends TextEditingController { return TextSpan(text: text, style: style); } - for (int i = 0; i < mistakes.length; i++) { - final mistake = mistakes[i]; - int previousMistakePosition = 0; - if (i > 0) { - final previousMistake = mistakes[i - 1]; - previousMistakePosition = - previousMistake.offset + previousMistake.length; - } - final mistakeStart = mistake.offset; - final mistakeEnd = mistakeStart + mistake.length; + final lastMistakeIndex = mistakes.length - 1; - children.add( - TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), - ); + try { + for (int i = 0; i < mistakes.length; i++) { + final mistake = mistakes[i]; + int previousMistakePosition = 0; + if (i > 0) { + final previousMistake = mistakes[i - 1]; + previousMistakePosition = + previousMistake.offset + previousMistake.length; + } + final mistakeStart = mistake.offset; + final mistakeEnd = mistakeStart + mistake.length; - final textStyle = style ?? const TextStyle(); - children.add( - TextSpan( - text: text.substring(mistakeStart, mistakeEnd), - style: textStyle.copyWith( - decoration: TextDecoration.underline, - decorationColor: Colors.red, - decorationThickness: underlineThickness, - backgroundColor: Colors.red.withOpacity(backgroundOpacity), - ), - ), - ); + children.add( + TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), + ); - if (i == mistakes.length - 1) { + final textStyle = style ?? const TextStyle(); children.add( TextSpan( - text: text.substring( - mistakeEnd, + text: text.substring(mistakeStart, mistakeEnd), + style: textStyle.copyWith( + decoration: TextDecoration.underline, + decorationColor: Colors.red, + decorationThickness: underlineThickness, + backgroundColor: Colors.red.withOpacity(backgroundOpacity), ), ), ); + + if (i == lastMistakeIndex) { + children.add( + TextSpan( + text: text.substring( + mistakeEnd, + ), + ), + ); + } } + } catch (e) { + return TextSpan(text: text, style: style); } return TextSpan(children: children, style: style); From a12872471ea5ac454bb1f9241557c2a46eecf689 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 25 Apr 2023 17:23:03 +0300 Subject: [PATCH 4/7] Add dot in the end of the comment --- lib/presentation/language_tool_text_field.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/language_tool_text_field.dart b/lib/presentation/language_tool_text_field.dart index 813f359..480e8c2 100644 --- a/lib/presentation/language_tool_text_field.dart +++ b/lib/presentation/language_tool_text_field.dart @@ -16,7 +16,7 @@ class LanguageToolTextField extends StatefulWidget { /// A builder function used to build errors. final Widget Function()? mistakeBuilder; - /// A text controller used to highlight errors + /// A text controller used to highlight errors. final LanguageToolTextEditingController? controller; /// Creates a widget that checks grammar errors. From 98a5fc8281c1b307a7ca7f9e40ccfdf13889a453 Mon Sep 17 00:00:00 2001 From: Gleb Date: Wed, 26 Apr 2023 12:20:49 +0300 Subject: [PATCH 5/7] Add text length check --- ...language_tool_text_editing_controller.dart | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/presentation/widgets/language_tool_text_editing_controller.dart b/lib/presentation/widgets/language_tool_text_editing_controller.dart index dc88ec9..98de964 100644 --- a/lib/presentation/widgets/language_tool_text_editing_controller.dart +++ b/lib/presentation/widgets/language_tool_text_editing_controller.dart @@ -29,47 +29,52 @@ class LanguageToolTextEditingController extends TextEditingController { final lastMistakeIndex = mistakes.length - 1; - try { - for (int i = 0; i < mistakes.length; i++) { - final mistake = mistakes[i]; - int previousMistakePosition = 0; - if (i > 0) { - final previousMistake = mistakes[i - 1]; - previousMistakePosition = - previousMistake.offset + previousMistake.length; - } - final mistakeStart = mistake.offset; - final mistakeEnd = mistakeStart + mistake.length; + for (int i = 0; i < mistakes.length; i++) { + final mistake = mistakes[i]; + int previousMistakePosition = 0; + if (i > 0) { + final previousMistake = mistakes[i - 1]; + previousMistakePosition = + previousMistake.offset + previousMistake.length; + } + final mistakeStart = mistake.offset; + final mistakeEnd = mistakeStart + mistake.length; + if (mistakeEnd > text.length) { children.add( - TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), + TextSpan( + text: text.substring(previousMistakePosition), + ), ); + break; + } + + children.add( + TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), + ); - final textStyle = style ?? const TextStyle(); + final textStyle = style ?? const TextStyle(); + children.add( + TextSpan( + text: text.substring(mistakeStart, mistakeEnd), + style: textStyle.copyWith( + decoration: TextDecoration.underline, + decorationColor: Colors.red, + decorationThickness: underlineThickness, + backgroundColor: Colors.red.withOpacity(backgroundOpacity), + ), + ), + ); + + if (i == lastMistakeIndex) { children.add( TextSpan( - text: text.substring(mistakeStart, mistakeEnd), - style: textStyle.copyWith( - decoration: TextDecoration.underline, - decorationColor: Colors.red, - decorationThickness: underlineThickness, - backgroundColor: Colors.red.withOpacity(backgroundOpacity), + text: text.substring( + mistakeEnd, ), ), ); - - if (i == lastMistakeIndex) { - children.add( - TextSpan( - text: text.substring( - mistakeEnd, - ), - ), - ); - } } - } catch (e) { - return TextSpan(text: text, style: style); } return TextSpan(children: children, style: style); From 0ec7c609cb5a99b55db3af31441eab5ed2c19f10 Mon Sep 17 00:00:00 2001 From: Gleb Date: Wed, 26 Apr 2023 12:46:01 +0300 Subject: [PATCH 6/7] Replace mistake TextSpan with WidgetSpan --- example/lib/app.dart | 41 ++++++++----------- ...language_tool_text_editing_controller.dart | 22 +++++++--- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/example/lib/app.dart b/example/lib/app.dart index e42dfd9..8134142 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -14,6 +14,23 @@ class App extends StatefulWidget { class _AppState extends State { final _langToolService = LangToolService(LanguageTool()); + final _textController = LanguageToolTextEditingController( + text: 'OKAYOKAYOKAYOKAYOKAY', + mistakes: [ + const Mistake( + message: 'bad', + type: 'bad', + offset: 0, + length: 3, + ), + const Mistake( + message: 'bad', + type: 'bad', + offset: 8, + length: 5, + ), + ], + ); @override Widget build(BuildContext context) { @@ -23,29 +40,7 @@ class _AppState extends State { padding: const EdgeInsets.all(20.0), child: LanguageToolTextField( langService: _langToolService, - controller: LanguageToolTextEditingController( - text: 'OKAYOKAYOKAY', - mistakes: [ - const Mistake( - message: 'bad', - type: 'bad', - offset: 2, - length: 2, - ), - const Mistake( - message: 'bad', - type: 'bad', - offset: 5, - length: 1, - ), - const Mistake( - message: 'bad', - type: 'bad', - offset: 7, - length: 3, - ), - ], - ), + controller: _textController, style: const TextStyle(), ), ), diff --git a/lib/presentation/widgets/language_tool_text_editing_controller.dart b/lib/presentation/widgets/language_tool_text_editing_controller.dart index 98de964..517bf45 100644 --- a/lib/presentation/widgets/language_tool_text_editing_controller.dart +++ b/lib/presentation/widgets/language_tool_text_editing_controller.dart @@ -19,7 +19,7 @@ class LanguageToolTextEditingController extends TextEditingController { TextStyle? style, required bool withComposing, }) { - final children = []; + final children = []; const underlineThickness = 2.0; const backgroundOpacity = 0.2; @@ -54,24 +54,34 @@ class LanguageToolTextEditingController extends TextEditingController { ); final textStyle = style ?? const TextStyle(); + final mistakeText = text.substring(mistakeStart, mistakeEnd); + + // WidgetSpans with mistake text characters are used here to calculate the correct caret position, which can be incorrectly positioned because of the WidgetSpan issue, described here: https://github.com/flutter/flutter/issues/107432. + // TextSpan recognizer to process clicks can't be used, because it requires the RichText widget instead of TextField, which we are using. Issue described here: https://github.com/flutter/flutter/issues/34931 + children.add( TextSpan( - text: text.substring(mistakeStart, mistakeEnd), style: textStyle.copyWith( decoration: TextDecoration.underline, decorationColor: Colors.red, decorationThickness: underlineThickness, backgroundColor: Colors.red.withOpacity(backgroundOpacity), ), + children: [ + for (final mistakeCharacter in mistakeText.characters) + WidgetSpan( + child: Text( + mistakeCharacter, + style: style, + ), + ), + ], ), ); - if (i == lastMistakeIndex) { children.add( TextSpan( - text: text.substring( - mistakeEnd, - ), + text: text.substring(mistakeEnd), ), ); } From 78ae80989c82d67dcb5e7a92beafbbd7db061b4c Mon Sep 17 00:00:00 2001 From: Gleb Date: Wed, 26 Apr 2023 15:32:08 +0300 Subject: [PATCH 7/7] Fix after review --- lib/domain/mistake.dart | 3 ++ ...language_tool_text_editing_controller.dart | 30 +++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/domain/mistake.dart b/lib/domain/mistake.dart index ecbd3a8..25a1b85 100644 --- a/lib/domain/mistake.dart +++ b/lib/domain/mistake.dart @@ -17,6 +17,9 @@ class Mistake { /// Sorted by probability. final List replacements; + /// A range of this mistake from offset to the end. + int get range => offset + length; + /// Creates a new instance of the [Mistake] class. const Mistake({ required this.message, diff --git a/lib/presentation/widgets/language_tool_text_editing_controller.dart b/lib/presentation/widgets/language_tool_text_editing_controller.dart index 517bf45..c62b760 100644 --- a/lib/presentation/widgets/language_tool_text_editing_controller.dart +++ b/lib/presentation/widgets/language_tool_text_editing_controller.dart @@ -28,40 +28,45 @@ class LanguageToolTextEditingController extends TextEditingController { } final lastMistakeIndex = mistakes.length - 1; - for (int i = 0; i < mistakes.length; i++) { final mistake = mistakes[i]; - int previousMistakePosition = 0; + int previousMistakeEnd = 0; if (i > 0) { final previousMistake = mistakes[i - 1]; - previousMistakePosition = - previousMistake.offset + previousMistake.length; + previousMistakeEnd = previousMistake.offset + previousMistake.length; } final mistakeStart = mistake.offset; - final mistakeEnd = mistakeStart + mistake.length; - if (mistakeEnd > text.length) { + if (mistake.range > text.length) { children.add( TextSpan( - text: text.substring(previousMistakePosition), + text: text.substring(previousMistakeEnd), ), ); break; } children.add( - TextSpan(text: text.substring(previousMistakePosition, mistakeStart)), + TextSpan(text: text.substring(previousMistakeEnd, mistakeStart)), ); final textStyle = style ?? const TextStyle(); - final mistakeText = text.substring(mistakeStart, mistakeEnd); + final mistakeText = text.substring(mistakeStart, mistake.range); - // WidgetSpans with mistake text characters are used here to calculate the correct caret position, which can be incorrectly positioned because of the WidgetSpan issue, described here: https://github.com/flutter/flutter/issues/107432. - // TextSpan recognizer to process clicks can't be used, because it requires the RichText widget instead of TextField, which we are using. Issue described here: https://github.com/flutter/flutter/issues/34931 + // WidgetSpans with mistake text characters are used here to + // calculate the correct caret position, which can be + // incorrectly positioned because of the WidgetSpan issue, + // described here: https://github.com/flutter/flutter/issues/107432. + // + // TextSpan recognizer to process clicks can't be used, + // because it requires the RichText widget but the TextField + // widget does not contain one. + // Issue described here: https://github.com/flutter/flutter/issues/34931 children.add( TextSpan( style: textStyle.copyWith( + color: Colors.green, decoration: TextDecoration.underline, decorationColor: Colors.red, decorationThickness: underlineThickness, @@ -78,10 +83,11 @@ class LanguageToolTextEditingController extends TextEditingController { ], ), ); + if (i == lastMistakeIndex) { children.add( TextSpan( - text: text.substring(mistakeEnd), + text: text.substring(mistake.range), ), ); }