Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -7,8 +7,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
184 changes: 149 additions & 35 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,55 +20,169 @@ 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;
LanguageToolController? _spellCheckController;

LanguageToolController _nonNullController() {
return _spellCheckController ??= LanguageToolController(
languageCheckService: InMemoryDictionaryLanguageCheckService(
getDictionary: () => _dictionary,
),
);
}

@override
Widget build(BuildContext context) {
final spellCheckController = _nonNullController();

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});
},
);
},
),
),
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),
),
),
],
),
),
),
),
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"),
],
),
],
],
),
),
),
);
}

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: 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/in_memory_dictionary_language_check_service.dart';
export 'src/wrappers/throttling_language_check_service.dart';
12 changes: 11 additions & 1 deletion lib/src/core/controllers/language_tool_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,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 @@ -182,6 +182,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