Skip to content

Commit

Permalink
Created text editing controller which builds TextSpans from list of M…
Browse files Browse the repository at this point in the history
…istake
  • Loading branch information
nazarski committed Apr 27, 2023
1 parent 5ca884f commit 7633590
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 4 deletions.
107 changes: 107 additions & 0 deletions lib/core/controllers/colored_text_editing_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import 'dart:developer';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:languagetool_textfield/domain/mistake.dart';

/// A TextEditingController with overrides buildTextSpan for building
/// marked TextSpans with tap recognizer
class ColoredTextEditingController extends TextEditingController {
/// List which contains Mistake objects spans are built from
List<Mistake> _mistakes = [];

final double _backGroundOpacity =
0.2; // background opacity for mistake TextSpan

final double _mistakeLineThickness =
1.5; // mistake TextSpan underline thickness

/// A method sets new list of Mistake and triggers buildTextSpan
void setMistakes(List<Mistake> list) {
_mistakes = list;
notifyListeners();
}

/// builds TextSpan from Mistake list
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
int currentOffset = 0; // enter index
final List<TextSpan> spans = []; // List of TextSpan
final int textLength = text.length; // Length of text to be built

/// Iterates _mistakes and adds TextSpans from Mistake offset and length
for (final Mistake mistake in _mistakes) {
/// Breaks the loop if iterated Mistake offset is bigger than text length.
if (mistake.offset > textLength ||
mistake.offset + mistake.length > textLength) {
break;
}

/// TextSpan before mistake
spans.add(
TextSpan(
text: text.substring(
currentOffset,
mistake.offset,
),
style: style,
),
);

/// Setting color of the mistake by its type
final Color mistakeColor = _getMistakeColor(mistake.type);

/// The mistake TextSpan
spans.add(
TextSpan(
text: text.substring(mistake.offset, mistake.offset + mistake.length),
mouseCursor: MaterialStateMouseCursor.clickable,
recognizer: TapGestureRecognizer()
..onTapDown = _callOverlay, // calls overlay with mistakes details
style: style?.copyWith(
backgroundColor: mistakeColor.withOpacity(_backGroundOpacity),
decoration: TextDecoration.underline,
decorationColor: mistakeColor,
decorationThickness: _mistakeLineThickness,
),
),
);

/// Changing enter index position for the next iteration
currentOffset = mistake.offset + mistake.length;
}

/// TextSpan after mistake
spans.add(
TextSpan(
text: text.substring(currentOffset),
style: style,
),
);

/// Returns TextSpan
return TextSpan(children: spans);
}

void _callOverlay(TapDownDetails details) {
log(details.globalPosition.toString());
}

/// Returns color for mistake TextSpan style
Color _getMistakeColor(String type) {
switch (type) {
case 'misspelling':
return Colors.red;
case 'style':
return Colors.blue;
case 'uncategorized':
return Colors.amber;
default:
return Colors.green;
}
}
}
6 changes: 6 additions & 0 deletions lib/domain/mistake.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ class Mistake {
required this.length,
this.replacements = const [],
});

@override
String toString() {
return 'Mistake{message: $message, type: $type, offset: $offset, '
'length: $length, replacements: $replacements}';
}
}
10 changes: 7 additions & 3 deletions lib/implementations/debounce_lang_tool_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ class DebounceLangToolService extends LanguageCheckService {
) : debouncing = Debouncing(duration: debouncingDuration);

@override
Future<List<Mistake>> findMistakes(String text) =>
debouncing.debounce(() => baseService.findMistakes(text))
as Future<List<Mistake>>;
Future<List<Mistake>> findMistakes(String text) async {
final value = await debouncing.debounce(() {
return baseService.findMistakes(text);
}) as List<Mistake>?;

return value ?? [];
}
}
9 changes: 9 additions & 0 deletions lib/languagetool_textfield.dart
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
library languagetool_textfield;

export 'package:language_tool/language_tool.dart';

export 'domain/language_check_service.dart';
export 'domain/mistake.dart';
export 'implementations/debounce_lang_tool_service.dart';
export 'implementations/lang_tool_service.dart';
export 'implementations/throttling_lang_tool_service.dart';
export "presentation/language_tool_text_field.dart";
33 changes: 32 additions & 1 deletion lib/presentation/language_tool_text_field.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import 'package:flutter/material.dart';
import 'package:languagetool_textfield/core/controllers/colored_text_editing_controller.dart';
import 'package:languagetool_textfield/domain/language_check_service.dart';

/// A TextField widget that checks the grammar using the given [langService]
Expand Down Expand Up @@ -29,8 +31,37 @@ class LanguageToolTextField extends StatefulWidget {
}

class _LanguageToolTextFieldState extends State<LanguageToolTextField> {
final ColoredTextEditingController _controller =
ColoredTextEditingController();
final int _maxLines = 8; // max lines of the TextFiled

/// Sends API request to get a list of Mistake
Future<void> _check(String text) async {
final list = await widget.langService.findMistakes(text);
if (list.isNotEmpty) {
_controller.setMistakes(list);
}
}

@override
void dispose() {
super.dispose();
_controller.dispose(); // disposes controller
}

@override
Widget build(BuildContext context) {
return const Placeholder();
return Padding(
padding: const EdgeInsets.all(24.0),
child: Center(
child: TextField(
controller: _controller,
maxLines: _maxLines,
onChanged: _check,
style: widget.style,
decoration: widget.decoration,
),
),
);
}
}

0 comments on commit 7633590

Please sign in to comment.