Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created text editing controller which builds TextSpans from list of M… #19

Merged
merged 14 commits into from
Apr 28, 2023
Merged
157 changes: 102 additions & 55 deletions lib/core/controllers/colored_text_editing_controller.dart
Original file line number Diff line number Diff line change
@@ -1,107 +1,154 @@
import 'dart:developer';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:languagetool_textfield/core/enums/mistake_type.dart';
import 'package:languagetool_textfield/domain/highlight_colors.dart';
import 'package:languagetool_textfield/domain/mistake.dart';

/// A TextEditingController with overrides buildTextSpan for building
/// marked TextSpans with tap recognizer
class ColoredTextEditingController extends TextEditingController {
/// Color scheme to highlight mistakes
final HighlightColors? highlightColorScheme;

/// List which contains Mistake objects spans are built from
List<Mistake> _mistakes = [];

final double _backGroundOpacity =
final double _backgroundOpacity =
nazarski marked this conversation as resolved.
Show resolved Hide resolved
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();
@override
set value(TextEditingValue newValue) {
_handleTextChange(newValue.text);
super.value = newValue;
}

/// builds TextSpan from Mistake list
/// Controller constructor
ColoredTextEditingController({this.highlightColorScheme});

/// Generates 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(
final int textLength = text.length;

/// Generator function to create TextSpan instances
Iterable<TextSpan> generateSpans() sync* {
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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mistake.offset + mistake.length
I see this used thrice in this method. DRY?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still seeing this line being repeated. Please review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swear i got rid of it in recent commits

break;
}

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

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

/// The mistake TextSpan
spans.add(
TextSpan(
/// Mistake highlighted TextSpan
yield 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),
backgroundColor: mistakeColor.withOpacity(_backgroundOpacity),
decoration: TextDecoration.underline,
decorationColor: mistakeColor,
decorationThickness: _mistakeLineThickness,
),
),
);
);

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

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

/// Returns TextSpan
return TextSpan(children: spans);
return TextSpan(children: generateSpans().toList());
}

void _callOverlay(TapDownDetails details) {
log(details.globalPosition.toString());
/// Apply changes to Mistake list while new data being fetched
void _handleTextChange(String newText) {
final int deltaLength = newText.length - text.length;

/// Update the _mistakes list in-place based on the text modifications
_mistakes = _mistakes
.map((mistake) {
int newOffset = mistake.offset;
int newLength = mistake.length;

/// If the text modification starts within the mistake
if (selection.start >= mistake.offset &&
nazarski marked this conversation as resolved.
Show resolved Hide resolved
selection.start <= mistake.offset + mistake.length) {
newLength += deltaLength;
}

/// If the text modification starts before the mistake
else if (selection.start < mistake.offset) {
newOffset += deltaLength;
}

/// Return the updated mistake (if the length is greater than 0)
return newLength > 0
? Mistake(
message: mistake.message,
type: mistake.type,
offset: newOffset,
length: newLength,
replacements: mistake.replacements,
)
: null;
})
.whereType<Mistake>()
.toList();

/// Notify listeners to rebuild the widget
notifyListeners();
}

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

/// Returns color for mistake TextSpan style
Color _getMistakeColor(String type) {
Color _getMistakeColor(MistakeType type) {
switch (type) {
case 'misspelling':
return Colors.red;
case 'style':
return Colors.blue;
case 'uncategorized':
return Colors.amber;
default:
return Colors.green;
case MistakeType.misspelling:
return highlightColorScheme?.misspellingMistakeColor ?? Colors.red;
case MistakeType.typographical:
return highlightColorScheme?.typographicalMistakeColor ?? Colors.green;
case MistakeType.grammar:
return highlightColorScheme?.grammarMistakeColor ?? Colors.amber;
case MistakeType.uncategorized:
return highlightColorScheme?.uncategorizedMistakeColor ?? Colors.blue;
case MistakeType.nonConformance:
return highlightColorScheme?.nonConformanceMistakeColor ??
Colors.greenAccent;
case MistakeType.style:
return highlightColorScheme?.styleMistakeColor ??
Colors.deepPurpleAccent;
case MistakeType.other:
return highlightColorScheme?.otherMistakeColor ?? Colors.white60;
}
}
}
23 changes: 23 additions & 0 deletions lib/core/enums/mistake_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
///Enumerate several mistake types
enum MistakeType {
/// Misspelling mistake type
misspelling,

/// Typographical mistake type
typographical,

/// Grammar mistake type
grammar,

/// Uncategorized mistake type
uncategorized,

/// NonConformance mistake type
nonConformance,

/// Style mistake type
style,

/// Any other mistake type
other,
}
36 changes: 36 additions & 0 deletions lib/domain/highlight_colors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'dart:ui';

/// Class creates color scheme for highlighting mistakes
class HighlightColors {
/// Misspelling mistake highlight color
final Color? misspellingMistakeColor;

/// Misspelling mistake highlight color
final Color? typographicalMistakeColor;

/// Typographical mistake highlight color
final Color? grammarMistakeColor;

/// Uncategorized mistake highlight color
final Color? uncategorizedMistakeColor;

/// NonConformance mistake highlight color
final Color? nonConformanceMistakeColor;

/// Style mistake highlight color
final Color? styleMistakeColor;

/// Other mistake highlight color
final Color? otherMistakeColor;
nazarski marked this conversation as resolved.
Show resolved Hide resolved

///Color scheme constructor
HighlightColors(
this.misspellingMistakeColor,
this.typographicalMistakeColor,
this.grammarMistakeColor,
this.uncategorizedMistakeColor,
this.nonConformanceMistakeColor,
this.styleMistakeColor,
this.otherMistakeColor,
);
}
10 changes: 3 additions & 7 deletions lib/domain/mistake.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:languagetool_textfield/core/enums/mistake_type.dart';

/// A data model class that stores information about a single writing mistake.
class Mistake {
/// A brief description of the mistake.
final String message;

/// A type of this mistake.
final String type;
final MistakeType type;

/// A position of the beginning of this mistake.
final int offset;
Expand All @@ -25,10 +27,4 @@ class Mistake {
required this.length,
this.replacements = const [],
});

@override
String toString() {
return 'Mistake{message: $message, type: $type, offset: $offset, '
'length: $length, replacements: $replacements}';
}
}
24 changes: 23 additions & 1 deletion lib/implementations/lang_tool_service.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:language_tool/language_tool.dart';
import 'package:languagetool_textfield/core/enums/mistake_type.dart';
import 'package:languagetool_textfield/domain/language_check_service.dart';
import 'package:languagetool_textfield/domain/mistake.dart';

Expand All @@ -16,7 +17,9 @@ class LangToolService extends LanguageCheckService {
final mistakes = writingMistakes.map(
(m) => Mistake(
message: m.message,
type: m.issueType,
type: _stringToMistakeType(
m.issueType,
),
offset: m.offset,
length: m.length,
replacements: m.replacements,
Expand All @@ -25,4 +28,23 @@ class LangToolService extends LanguageCheckService {

return mistakes.toList();
}

MistakeType _stringToMistakeType(String issueType) {
switch (issueType.toLowerCase()) {
case 'misspelling':
return MistakeType.misspelling;
case 'typographical':
return MistakeType.typographical;
case 'grammar':
return MistakeType.grammar;
case 'uncategorized':
return MistakeType.uncategorized;
case 'non-conformance':
return MistakeType.nonConformance;
case 'style':
return MistakeType.style;
default:
return MistakeType.other;
}
}
}
1 change: 1 addition & 0 deletions lib/languagetool_textfield.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ library languagetool_textfield;

export 'package:language_tool/language_tool.dart';

export 'domain/highlight_colors.dart';
export 'domain/language_check_service.dart';
export 'domain/mistake.dart';
export 'implementations/debounce_lang_tool_service.dart';
Expand Down
Loading