Highlight parts of text selected by user in flutter - flutter

So, i'm working on an annotation app with flutter. The user selects a range of text in a selectableText Field and that text has to be highlighted. I'm using selectableText widget to get selected text.Is there a way?
This is a screenshot of what i'm trying to do.
Every highlighted part of text is a text that the user already selected and assigned a tag to.
[1]: https://i.stack.imgur.com/33qCL.png
So far, i've found this code that allows to highlight one selection :
import 'package:flutter/material.dart';
class HighlightText extends StatelessWidget {
final String text;
final String highlight;
final TextStyle style;
final TextStyle highlightStyle;
final Color highlightColor;
final bool ignoreCase;
HighlightText({
Key key,
this.text,
this.highlight,
this.style,
this.highlightColor,
TextStyle highlightStyle,
this.ignoreCase: false,
}) : assert(
highlightColor == null || highlightStyle == null,
'highlightColor and highlightStyle cannot be provided at same time.',
),
highlightStyle = highlightStyle ?? style?.copyWith(color: highlightColor) ?? TextStyle(color: highlightColor),
super(key: key);
#override
Widget build(BuildContext context) {
final text = this.text ?? '';
if ((highlight?.isEmpty ?? true) || text.isEmpty) {
return Text(text, style: style);
}
var sourceText = ignoreCase ? text.toLowerCase() : text;
var targetHighlight = ignoreCase ? highlight.toLowerCase() : highlight;
List<TextSpan> spans = [];
int start = 0;
int indexOfHighlight;
do {
indexOfHighlight = sourceText.indexOf(targetHighlight, start);
if (indexOfHighlight < 0) {
// no highlight
spans.add(_normalSpan(text.substring(start)));
break;
}
if (indexOfHighlight > start) {
// normal text before highlight
spans.add(_normalSpan(text.substring(start, indexOfHighlight)));
}
start = indexOfHighlight + highlight.length;
spans.add(_highlightSpan(text.substring(indexOfHighlight, start)));
} while (true);
return Text.rich(TextSpan(children: spans));
}
TextSpan _highlightSpan(String content) {
return TextSpan(text: content, style: highlightStyle);
}
TextSpan _normalSpan(String content) {
return TextSpan(text: content, style: style);
}
}
And i'm trying to adapt it so that i can highlight multiple parts.

Related

Change color part of text using index in flutter

I have a one String which contains words: achievement admission advertise pencil. I have a list with pairs of numbers:
class Pair<T1, T2> {
final T1 a;
final T2 b;
Pair(this.a, this.b);
}
String letters = "achievement admission advertise pencil";
List<Pair> words = [Pair(3, 5), Pair(6, 8), Pair(9, 11), Pair(12, 14), Pair(15, 17)];
I want to change color part of String using index from which sign to which sign. For example after 2 seconds letters from 3 to 5 should have color green. After next 2 seconds only letters from 6 to 8 should be green, after next 2 seconds only letters from 9 to 11 should be green, rest letters should return to black. There is any way to do that?
Yes, you can create a custom widget for this. Here is a working example:
(Use with SyllableText(text: letters, parts: words))
class SyllableText extends StatefulWidget {
const SyllableText({
required this.text,
required this.parts,
Key? key,
}) : super(key: key);
final String text;
final List<Pair> parts;
#override
SyllableTextState createState() => SyllableTextState();
}
class SyllableTextState extends State<SyllableText> {
int currentPartIndex = 0;
#override
void initState() {
super.initState();
Future.doWhile(() async {
await Future.delayed(Duration(seconds: 2));
if (mounted && currentPartIndex < widget.parts.length) {
setState(() => currentPartIndex++);
return true;
} else {
return false;
}
});
}
#override
Widget build(BuildContext context) {
if (currentPartIndex < widget.parts.length) {
final part = widget.parts[currentPartIndex];
final startText = widget.text.substring(0, part.a);
final coloredText = widget.text.substring(part.a, part.b + 1);
final endText = widget.text.substring(part.b + 1);
return Text.rich(
TextSpan(
children: [
TextSpan(text: startText),
TextSpan(text: coloredText, style: TextStyle(color: Colors.green)),
TextSpan(text: endText),
],
),
);
} else {
return Text(widget.text);
}
}
}

Flutter using hyphenation with \u00ad in a Text widget and only if applied then replace with a soft hyphen (-)

Is there is a way to do hyphenation with \u00ad in a Text widget and only if the hyphenation is applied then it should replace a soft hyphen symbol (-) on the word break?
for example:
Hyphen\u00adation text
should look like if text is not breaking
Hyphenation text
or like this if the text is splitted up in two lines with a (-) symbol
Hyphen-
ation text
I just wrote this StatelessWidget for that purpose:
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class TextWithHyphenation extends StatelessWidget {
const TextWithHyphenation(
this.data, {
Key? key,
this.style,
}) : super(key: key);
final String data;
final TextStyle? style;
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, size) {
final String text = data;
final span = TextSpan(text: text, style: style);
final textPainter = TextPainter(
text: span, maxLines: 2, textDirection: ui.TextDirection.ltr)
..layout(maxWidth: size.maxWidth);
final TextSelection selection =
TextSelection(baseOffset: 0, extentOffset: text.length);
final List<TextBox> boxes = textPainter.getBoxesForSelection(selection);
final int lines = boxes.length;
if (lines > 1) {
// The text has more than a line
return Text(text.replaceFirst('\u00ad', '\u00ad-'), style: style);
} else {
return Text(text, style: style);
}
});
}
}
Note, that this only works for one \u00ad char inside a String resource.

Flutter Widget that shows text line by line?

Is there a widget in Flutter that allows children Text widgets to be shown line by line at every press of the widget? This should act similar to how bulleted lines in a powerpoint presentation act after every click.
I just tested and it works, probably I did not cover some edge cases, but it is not a lot of work...
//I would call it TIM like VIM :P
class TextIMproved extends StatefulWidget {
final String _longString;
final int _numberOfWordsPerRow;
TextIMproved(this._numberOfWordsPerRow, this._longString);
#override
_TextIMprovedState createState() => _TextIMprovedState(_numberOfWordsPerRow, _longString);
}
class _TextIMprovedState extends State<TextIMproved> {
final String longString;
List<String> listString;
int _numberOfWordsPerRow;
String strPopulated;
String strToDisplay='';
_TextIMprovedState(this._numberOfWordsPerRow, this.longString);
#override
void initState() {
super.initState();
listString = longString.split(' ');
splitString();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
child: Text(strToDisplay, maxLines: longString.length ~/ _numberOfWordsPerRow + _numberOfWordsPerRow,),
),
onTap: splitString,
);
}
void splitString() {
//1.prepare empty string
strPopulated = '';
//2.populate string
int len = listString.length <_numberOfWordsPerRow ? listString.length : _numberOfWordsPerRow;
for (int i = 0; i < len; i++) {
strPopulated += listString[i] + ' ';
}
//3. display portion of string
setState(() {
strToDisplay += strPopulated;
});
//4.remove displayed text
List<String> listToremove = strPopulated.split(' ');
for (String str in listToremove) {
listString.remove(str);
}
}
}
EDIT:
you can add animation to this widget...

How to change color of particular text in a text field dynamically?

Consider bellow image, I want to dynamically change the text color of part of the text based on the user input text (not the whole text) in a text field. How can i do that in flutter?
For this example we actually don't need a full blown rich-text editor.
I had a similar goal in my app to highlight tags (#flutter) or date references (next week, on Friday, etc) and I was able to implement this by extending built-in EditableText widget and posted my example as a Gist here: https://gist.github.com/pulyaevskiy/d7af7217c2e71f31dfb78699f91dfbb5
Below is full implementation of this widget which I called AnnotatedEditableText.
There is new property annotations which describes ranges of text that need to be highlighted and their style.
import 'package:flutter/widgets.dart';
class Annotation extends Comparable<Annotation> {
Annotation({#required this.range, this.style});
final TextRange range;
final TextStyle style;
#override
int compareTo(Annotation other) {
return range.start.compareTo(other.range.start);
}
#override
String toString() {
return 'Annotation(range:$range, style:$style)';
}
}
class AnnotatedEditableText extends EditableText {
AnnotatedEditableText({
Key key,
FocusNode focusNode,
TextEditingController controller,
TextStyle style,
ValueChanged<String> onChanged,
ValueChanged<String> onSubmitted,
Color cursorColor,
Color selectionColor,
TextSelectionControls selectionControls,
this.annotations,
}) : super(
key: key,
focusNode: focusNode,
controller: controller,
cursorColor: cursorColor,
style: style,
keyboardType: TextInputType.text,
autocorrect: true,
autofocus: true,
selectionColor: selectionColor,
selectionControls: selectionControls,
onChanged: onChanged,
onSubmitted: onSubmitted,
);
final List<Annotation> annotations;
#override
AnnotatedEditableTextState createState() => new AnnotatedEditableTextState();
}
class AnnotatedEditableTextState extends EditableTextState {
#override
AnnotatedEditableText get widget => super.widget;
List<Annotation> getRanges() {
var source = widget.annotations;
source.sort();
var result = new List<Annotation>();
Annotation prev;
for (var item in source) {
if (prev == null) {
// First item, check if we need one before it.
if (item.range.start > 0) {
result.add(new Annotation(
range: TextRange(start: 0, end: item.range.start),
));
}
result.add(item);
prev = item;
continue;
} else {
// Consequent item, check if there is a gap between.
if (prev.range.end > item.range.start) {
// Invalid ranges
throw new StateError(
'Invalid (intersecting) ranges for annotated field');
} else if (prev.range.end < item.range.start) {
result.add(Annotation(
range: TextRange(start: prev.range.end, end: item.range.start),
));
}
// Also add current annotation
result.add(item);
prev = item;
}
}
// Also check for trailing range
final String text = textEditingValue.text;
if (result.last.range.end < text.length) {
result.add(Annotation(
range: TextRange(start: result.last.range.end, end: text.length),
));
}
return result;
}
#override
TextSpan buildTextSpan() {
final String text = textEditingValue.text;
if (widget.annotations != null) {
var items = getRanges();
var children = <TextSpan>[];
for (var item in items) {
children.add(
TextSpan(style: item.style, text: item.range.textInside(text)),
);
}
return new TextSpan(style: widget.style, children: children);
}
return new TextSpan(style: widget.style, text: text);
}
}
Rich text controller works fine!
See more on https://pub.dev/packages/rich_text_controller
First you choose your RegExp
RichTextController _controller;
Map<RegExp, TextStyle> patternUser = {
RegExp(r"\B#[a-zA-Z0-9]+\b"):
TextStyle(color: Colors.amber, fontWeight: FontWeight.bold)
};
on initState()
_controller = RichTextController(
patternMap: patternUser,
);
Add controller on your TextFormField
TextFormField(
controller: _controller,
style: TextStyle(color: Colors.white),
)

Style part of text in TextField

I'm implementing a custom text field and I would like to style certain keywords (namely hashtags) differently than the rest of the text as the user type them in.
Kind of like this:
Is there a way to do that in Flutter ?
This question is very similar to How to change color of particular text in a text field dynamically?
I answered it there in: https://stackoverflow.com/a/57846261/5280562
In short: you can extend EditableText widget including its EditableTextState class and override buildTextSpan method.
Below is a working example called AnnotatedEditableText that I use in my app.
You need to supply a list of Annotation objects which describe which ranges of text need to be highlighted and what style to use.
import 'package:flutter/widgets.dart';
class Annotation extends Comparable<Annotation> {
Annotation({#required this.range, this.style});
final TextRange range;
final TextStyle style;
#override
int compareTo(Annotation other) {
return range.start.compareTo(other.range.start);
}
#override
String toString() {
return 'Annotation(range:$range, style:$style)';
}
}
class AnnotatedEditableText extends EditableText {
AnnotatedEditableText({
Key key,
FocusNode focusNode,
TextEditingController controller,
TextStyle style,
ValueChanged<String> onChanged,
ValueChanged<String> onSubmitted,
Color cursorColor,
Color selectionColor,
TextSelectionControls selectionControls,
this.annotations,
}) : super(
key: key,
focusNode: focusNode,
controller: controller,
cursorColor: cursorColor,
style: style,
keyboardType: TextInputType.text,
autocorrect: true,
autofocus: true,
selectionColor: selectionColor,
selectionControls: selectionControls,
onChanged: onChanged,
onSubmitted: onSubmitted,
);
final List<Annotation> annotations;
#override
AnnotatedEditableTextState createState() => new AnnotatedEditableTextState();
}
class AnnotatedEditableTextState extends EditableTextState {
#override
AnnotatedEditableText get widget => super.widget;
List<Annotation> getRanges() {
var source = widget.annotations;
source.sort();
var result = new List<Annotation>();
Annotation prev;
for (var item in source) {
if (prev == null) {
// First item, check if we need one before it.
if (item.range.start > 0) {
result.add(new Annotation(
range: TextRange(start: 0, end: item.range.start),
));
}
result.add(item);
prev = item;
continue;
} else {
// Consequent item, check if there is a gap between.
if (prev.range.end > item.range.start) {
// Invalid ranges
throw new StateError(
'Invalid (intersecting) ranges for annotated field');
} else if (prev.range.end < item.range.start) {
result.add(Annotation(
range: TextRange(start: prev.range.end, end: item.range.start),
));
}
// Also add current annotation
result.add(item);
prev = item;
}
}
// Also check for trailing range
final String text = textEditingValue.text;
if (result.last.range.end < text.length) {
result.add(Annotation(
range: TextRange(start: result.last.range.end, end: text.length),
));
}
return result;
}
#override
TextSpan buildTextSpan() {
final String text = textEditingValue.text;
if (widget.annotations != null) {
var items = getRanges();
var children = <TextSpan>[];
for (var item in items) {
children.add(
TextSpan(style: item.style, text: item.range.textInside(text)),
);
}
return new TextSpan(style: widget.style, children: children);
}
return new TextSpan(style: widget.style, text: text);
}
}
It's also available in this Gist: https://gist.github.com/pulyaevskiy/d7af7217c2e71f31dfb78699f91dfbb5
I actually had the same problem and found the AnnotatedEditbleText which helped me a lot.
I published the helpful package to solve this kind of problem.
https://pub.dev/packages/hashtagable
The TextField does not provide that functionality.
https://pub.dartlang.org/packages/zefyr can do that though.
I think there are some more ''hard'' ways to do this
The first one:
Make a row widget, add a part of the String until the word you want to highlight, add the special word, style it and add the rest of your string.
Or, you could try RichText
Günter post it about the zefyr package, I didn't use it yet, but if suits you, I'll be glad that helped