Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6f05c15
style: run dart formatter
andrew-bekhiet-solid Sep 9, 2025
d701a6a
feat: add actions builder to allow adding actions
andrew-bekhiet-solid Sep 9, 2025
bcb02b5
refactor: avoid magic numbers
andrew-bekhiet-solid Sep 9, 2025
9de4a15
feat: add "add to dictionary" feature
andrew-bekhiet-solid Sep 10, 2025
4280367
doc: update changelog
andrew-bekhiet-solid Sep 10, 2025
5e53b8b
fix: add scroll view to scroll the ui
andrew-bekhiet-solid Sep 10, 2025
7075f62
reactor: allow changing language tool service and throttling duration
andrew-bekhiet-solid Sep 10, 2025
68d4d69
refactor: avoid widget returning methods
andrew-bekhiet-solid Sep 10, 2025
216d4fa
fix: handle invalid mistake offsets
andrew-bekhiet-solid Jun 2, 2026
4b9ca36
refactor: extract method
andrew-bekhiet-solid Jun 2, 2026
869eb32
style: fix lints
andrew-bekhiet-solid Jun 1, 2026
587d5dc
refactor: use recheckText
andrew-bekhiet-solid Jun 8, 2026
8a957fb
refactor: allow chaining of LanguageCheckServices
andrew-bekhiet-solid Jun 16, 2026
c56ac64
docs: fix code comment documentation
andrew-bekhiet-solid Jun 16, 2026
529a632
fix: recheck text after adding a word to dictionary
andrew-bekhiet-solid Jun 16, 2026
9e4efd4
fix: recheck that the mistake still exists before fixing
andrew-bekhiet-solid Jun 16, 2026
49c39cf
add clarifying comment
andrew-bekhiet-solid Jun 18, 2026
4e76986
refactor: rename to FilteredLanguageCheckService
andrew-bekhiet-solid Jun 18, 2026
cd6a84e
fix: import path
andrew-bekhiet-solid Jun 18, 2026
aa676f5
refactor: initialize _spellCheckController lazily
andrew-bekhiet-solid Jun 18, 2026
6938fd5
style: allow initialized late vars
andrew-bekhiet-solid Jun 18, 2026
e0de60a
style: inline var
andrew-bekhiet-solid Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
- potentially BREAKING: hide baseService and debouncing/throttling from Debouncing and Throttling LanguageService wrappers
- potentially BREAKING: rename `DebounceLanguageToolService` to `DebounceLanguageCheckService`
- potentially BREAKING: rename `ThrottleLanguageToolService` to `ThrottleLanguageCheckService`
- Support adding words to dictionary through `addWordToDictionary` callback in `LanguageToolMistakePopup`
- Allow overriding `languageCheckService`
- Add `isEnabled` to toggle spell check
- Use default IconButton padding for mistake popup
- Add missing properties from flutter's `TextField`
- autofillHints
- autofocus
Expand Down
5 changes: 5 additions & 0 deletions example/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
include: package:solid_lints/analysis_options.yaml

custom_lint:
rules:
- avoid_late_keyword:
allow_initialized: true
188 changes: 153 additions & 35 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,55 +20,173 @@ class App extends StatefulWidget {
}

class _AppState extends State<App> {
/// Initialize LanguageToolController
final LanguageToolController _controller = LanguageToolController();
Set<String> _dictionary = {};
final _addWordController = TextEditingController();

static const List<MainAxisAlignment> alignments = [
MainAxisAlignment.center,
MainAxisAlignment.start,
MainAxisAlignment.end,
];
int currentAlignmentIndex = 0;
late final LanguageToolController _spellCheckController =
LanguageToolController(
languageCheckService: FilteredLanguageCheckService(
languageCheckService: ThrottlingLanguageCheckService(
LanguageToolService(LanguageToolClient()),
const Duration(milliseconds: 250),
),
shouldIgnoreMistake: (mistake, text) {
final word = text.substring(mistake.offset, mistake.endOffset);

return _dictionary.contains(word);
},
),
);

@override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Column(
mainAxisAlignment: alignments[currentAlignmentIndex],
children: [
LanguageToolTextField(
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,
body: SingleChildScrollView(
child: Column(
children: [
LanguageToolTextField(
controller: _spellCheckController,
language: 'en-US',
mistakePopup: MistakePopup(
popupRenderer: PopupOverlayRenderer(),
mistakeBuilder: ({
required LanguageToolController controller,
required Mistake mistake,
required Offset mistakePosition,
required PopupOverlayRenderer popupRenderer,
}) {
return LanguageToolMistakePopup(
popupRenderer: popupRenderer,
mistake: mistake,
mistakePosition: mistakePosition,
controller: controller,
addWordToDictionary: (word) async {
setState(() => _dictionary = {..._dictionary, word});
_spellCheckController.recheckText();
},
Comment thread
andrew-bekhiet-solid marked this conversation as resolved.
);
},
),
),
),
DropdownMenu(
hintText: "Select alignment...",
onSelected: (value) => setState(() {
currentAlignmentIndex = value ?? 0;
}),
dropdownMenuEntries: const [
DropdownMenuEntry(value: 0, label: "Center alignment"),
DropdownMenuEntry(value: 1, label: "Top alignment"),
DropdownMenuEntry(value: 2, label: "Bottom alignment"),
],
),
],
ValueListenableBuilder(
valueListenable: _spellCheckController,
builder: (_, __, ___) => CheckboxListTile(
title: const Text("Enable spell checking"),
value: _spellCheckController.isEnabled,
onChanged: (value) =>
_spellCheckController.isEnabled = value ?? false,
),
),
const SizedBox(height: 20),
Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Dictionary',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: _addWordController,
decoration: const InputDecoration(
labelText: 'Add word to dictionary',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addWord(),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _addWord,
child: const Text('Add'),
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Dictionary Words (${_dictionary.length})',
style: const TextStyle(fontWeight: FontWeight.w500),
),
if (_dictionary.isNotEmpty)
TextButton(
onPressed: _clearAllWords,
child: const Text('Clear All'),
),
],
),
const SizedBox(height: 8),
if (_dictionary.isEmpty)
const Center(
child: Text(
'No words in dictionary',
style: TextStyle(color: Colors.grey),
),
)
else
for (final word in _dictionary)
ListTile(
title: Text(word),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeWord(word),
),
),
],
),
),
),
],
),
),
),
);
}

void _addWord() {
final word = _addWordController.text.trim();

if (word.isNotEmpty && !_dictionary.contains(word)) {
setState(() {
_dictionary = {..._dictionary, word};
_addWordController.clear();
_spellCheckController.recheckText();
});
}
}

void _removeWord(String word) {
setState(() {
_dictionary = _dictionary.difference({word});
_spellCheckController.recheckText();
});
}

void _clearAllWords() {
setState(() {
_dictionary = {};
_spellCheckController.recheckText();
});
}

@override
void dispose() {
_controller.dispose();
_spellCheckController.dispose();
_addWordController.dispose();
super.dispose();
}
}
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
solid_lints: ^0.0.14
solid_lints: ^0.3.1

flutter:
uses-material-design: true
2 changes: 2 additions & 0 deletions lib/languagetool_textfield.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ export 'src/language_check_services/language_tool_service.dart';
export 'src/presentation/language_tool_text_field.dart';
export 'src/utils/mistake_popup.dart';
export 'src/utils/popup_overlay_renderer.dart';
export 'src/utils/result.dart';
export 'src/wrappers/debounce_language_check_service.dart';
export 'src/wrappers/filtered_language_check_service.dart';
export 'src/wrappers/throttling_language_check_service.dart';
22 changes: 17 additions & 5 deletions lib/src/core/controllers/language_tool_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class LanguageToolController extends TextEditingController {

if (!_isEnabled) return;

_handleTextChange(text, spellCheckSameText: true);
recheckText();
}

/// Indicates whether spell checking is enabled
Expand All @@ -64,7 +64,7 @@ class LanguageToolController extends TextEditingController {
_isEnabled = value;

if (_isEnabled) {
_handleTextChange(text, spellCheckSameText: true);
recheckText();
} else {
_mistakes = [];
for (final recognizer in _recognizers) {
Expand Down Expand Up @@ -126,9 +126,11 @@ class LanguageToolController extends TextEditingController {
}) {
final languageToolService = LanguageToolService(languageToolClient);

// false positive, the same variable is used beyond the return statement
// ignore: avoid_unnecessary_return_variable
if (delay == Duration.zero) return languageToolService;
if (delay == Duration.zero) {
// false positive, the variable might be used after the if statement
// ignore: avoid_unnecessary_return_variable
return languageToolService;
}

switch (delayType) {
case DelayType.debouncing:
Expand Down Expand Up @@ -189,6 +191,16 @@ class LanguageToolController extends TextEditingController {
});
}

/// Rechecks the current text for spelling and grammar errors.
///
/// This method forces a recheck of the existing text
/// This is useful when you want to re-evaluate the text without any actual
/// text changes, such as after changing language settings or updating
/// spell check configurations.
void recheckText() {
_handleTextChange(text, spellCheckSameText: true);
}

/// Clear mistakes list when text mas modified and get a new list of mistakes
/// via API
Future<void> _handleTextChange(
Expand Down
Loading
Loading