diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cd6f0..8d43d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - BREAKING: require flutter 3.27.0 or higher - BREAKING: require dart 3.0.0 or higher - BREAKING: rename autoFocus to autofocus to match `TextField`'s naming +- Add `isEnabled` to toggle spell check - Add missing properties from flutter's `TextField` - autofillHints - autofocus diff --git a/example/lib/main.dart b/example/lib/main.dart index da7d074..968adf1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -41,6 +41,14 @@ class _AppState extends State { controller: _controller, language: 'en-US', ), + ValueListenableBuilder( + valueListenable: _controller, + builder: (_, __, ___) => CheckboxListTile( + title: const Text("Enable spell checking"), + value: _controller.isEnabled, + onChanged: (value) => _controller.isEnabled = value ?? false, + ), + ), DropdownMenu( hintText: "Select alignment...", onSelected: (value) => setState(() { diff --git a/lib/src/core/controllers/language_tool_controller.dart b/lib/src/core/controllers/language_tool_controller.dart index 5f94bae..eb39c0e 100644 --- a/lib/src/core/controllers/language_tool_controller.dart +++ b/lib/src/core/controllers/language_tool_controller.dart @@ -19,6 +19,8 @@ import 'package:languagetool_textfield/src/utils/mistake_popup.dart'; /// A TextEditingController with overrides buildTextSpan for building /// marked TextSpans with tap recognizer class LanguageToolController extends TextEditingController { + bool _isEnabled; + /// Color scheme to highlight mistakes final HighlightStyle highlightStyle; @@ -73,21 +75,46 @@ class LanguageToolController extends TextEditingController { _languageToolClient.language = language; } + /// Indicates whether spell checking is enabled + bool get isEnabled => _isEnabled; + + set isEnabled(bool value) { + if (value == _isEnabled) return; + + _isEnabled = value; + + if (_isEnabled) { + _handleTextChange(text, spellCheckSameText: true); + } else { + _mistakes = []; + for (final recognizer in _recognizers) { + recognizer.dispose(); + } + _recognizers.clear(); + + notifyListeners(); + } + } + /// An error that may have occurred during the API fetch. Object? get fetchError => _fetchError; @override set value(TextEditingValue newValue) { - _handleTextChange(newValue.text); + if (_isEnabled) { + _handleTextChange(newValue.text); + } + super.value = newValue; } /// Controller constructor LanguageToolController({ + bool isEnabled = true, this.highlightStyle = const HighlightStyle(), this.delay = Duration.zero, this.delayType = DelayType.debouncing, - }) { + }) : _isEnabled = isEnabled { _languageCheckService = _getLanguageCheckService(); } @@ -111,9 +138,17 @@ class LanguageToolController extends TextEditingController { @override TextSpan buildTextSpan({ required BuildContext context, - TextStyle? style, required bool withComposing, + TextStyle? style, }) { + if (!_isEnabled) { + return super.buildTextSpan( + context: context, + withComposing: withComposing, + style: style, + ); + } + final formattedTextSpans = _generateSpans( context, style: style, @@ -132,6 +167,10 @@ class LanguageToolController extends TextEditingController { /// Replaces mistake with given replacement void replaceMistake(Mistake mistake, String replacement) { + if (!_isEnabled) { + throw StateError('LanguageToolController is not enabled'); + } + final mistakes = List.from(_mistakes); mistakes.remove(mistake); _mistakes = mistakes; @@ -145,33 +184,38 @@ class LanguageToolController extends TextEditingController { /// Clear mistakes list when text mas modified and get a new list of mistakes /// via API - Future _handleTextChange(String newText) async { + Future _handleTextChange( + String newText, { + bool spellCheckSameText = false, + }) async { ///set value triggers each time, even when cursor changes its location ///so this check avoid cleaning Mistake list when text wasn't really changed - if (newText == text || newText.isEmpty) return; + if (spellCheckSameText || newText != text && newText.isNotEmpty) { + final filteredMistakes = _filterMistakesOnChanged(newText); + _mistakes = filteredMistakes.toList(); + + // If we have a text change and we have a popup on hold + // it will close the popup + _closePopup(); + + for (final recognizer in _recognizers) { + recognizer.dispose(); + } + _recognizers.clear(); + + final mistakesWrapper = + await _latestResponseService.processLatestOperation( + () => + _languageCheckService?.findMistakes(newText) ?? Future(() => null), + ); + if (mistakesWrapper == null || !mistakesWrapper.hasResult) return; - final filteredMistakes = _filterMistakesOnChanged(newText); - _mistakes = filteredMistakes.toList(); + final mistakes = mistakesWrapper.result(); + _fetchError = mistakesWrapper.error; - // If we have a text change and we have a popup on hold - // it will close the popup - _closePopup(); - - for (final recognizer in _recognizers) { - recognizer.dispose(); + _mistakes = mistakes; + notifyListeners(); } - _recognizers.clear(); - - final mistakesWrapper = await _latestResponseService.processLatestOperation( - () => _languageCheckService?.findMistakes(newText) ?? Future(() => null), - ); - if (mistakesWrapper == null || !mistakesWrapper.hasResult) return; - - final mistakes = mistakesWrapper.result(); - _fetchError = mistakesWrapper.error; - - _mistakes = mistakes; - notifyListeners(); } /// Generator function to create TextSpan instances @@ -200,7 +244,7 @@ class LanguageToolController extends TextEditingController { final Color mistakeColor = _getMistakeColor(mistake.type); /// Create a gesture recognizer for mistake - final _onTap = TapGestureRecognizer() + final onTap = TapGestureRecognizer() ..onTapDown = (details) { popupWidget?.show( context, @@ -223,7 +267,7 @@ class LanguageToolController extends TextEditingController { }; /// Adding recognizer to the list for future disposing - _recognizers.add(_onTap); + _recognizers.add(onTap); /// Mistake highlighted TextSpan yield TextSpan( @@ -242,7 +286,7 @@ class LanguageToolController extends TextEditingController { decorationColor: mistakeColor, decorationThickness: highlightStyle.mistakeLineThickness, ), - recognizer: _onTap, + recognizer: onTap, ), ], );