highlight keywords in string in flutter - flutter

I am new to flutter. I need to highlight keywords in string, normally using replace method we can achieve it but in flutter it seems difficult.
Here is the code I am trying.
Widget getString() {
List<TextSpan> lsTextSpan = [];
String str = 'This is new year and I have new bicycle.';
List<String> lskeywords = ['new year', 'bicycle'];
//here need code ?????
return RichText(
text: TextSpan(
children: lsTextSpan,
));
}

You can change regex pattern as you wish.
RegExp(r'[^a-zA-Z0-9]+') is replacing special characters except 'a-zA-Z0-9'.
RichText(
text: _getText(
text: 'This is new year and I have new bicycle.',
highlightedTextList: ['new', 'bicycle'],
),
),
TextSpan _getText({String text, List<String> highlightedTextList}) {
return TextSpan(
children: _highLight(
text: text,
highlightedTextList: highlightedTextList,
),
);
}
List<TextSpan> _highLight({String text, List<String> highlightedTextList}) {
var splittedText = text.split(' ');
var spans = <TextSpan>[];
var pattern = RegExp(r'[^a-zA-Z0-9]+');
for (var i = 0; i < splittedText.length; i++) {
var index = highlightedTextList.indexWhere((element) {
return splittedText[i].replaceAll(pattern, '') == element;
});
spans.add(
TextSpan(
text: '${splittedText[i]} ',
style: TextStyle(
color: index > -1 ? Colors.blueAccent : Colors.black,
),
),
);
}
return spans;
}
This is the result:

Related

Is there a way of making the first part of formatted string bold?

Is there a way of making "${innerItem.key}" bold? Thanks
List<Map<String, dynamic>> incidentList = [
for (final json in listNode.map((x) => x.toJson()))
{
'Code': json['field'][0]['field_value'],
'Description': json['field'][1]['field_value'],
'Organisation Unit': json['field'][2]['field_value'],
'Date Reported': json['field'][3]['field_value'],
'Status': json['field'][4]['field_value'],
'RunHyperlink' : json['run_hyperlink']
}
];
final List<String> values = [];
for(final item in incidentList){
String groupedElement = "";
for(var innerItem in item.entries)
{
groupedElement += "${innerItem.key} : ${innerItem.value}\n";
}
values.add(groupedElement);
}
await WriteCache.setListString(key: 'cache4', value: values);
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => LossEvent()));
The string will be stored in "values" and will then be accessed by "snapshot.data[index]". Is there a way of making "${innerItem.key} bold?" before storing it into "values"?
You are looking for RichText:
Text.rich(
TextSpan(
children: [
TextSpan(
text: "${innerItem.key}: ",
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: "${innerItem.value}\n"),
],
),
)
And of course, it will have to be a list of Widget, and not string
You can use the flutter_html package and the to put a String with HTML format
ex:
Html(
data: '<body><b>This text is gonna be bold</b><br>This gonna be normal</body>',
style: {
'body': Style(
fontSize: 18,
),
'b': Style(
fontSize: 18,
fontWeight: FontWeight.bold,
),
);
Also, you can customize the styling of your text using HTML tags(or your custom tags) from styles Map property.
To implement the html tags in the groupElement, just do it like this:
for(var innerItem in item.entries)
{
groupedElement += "<body><b>${innerItem.key}</b> : ${innerItem.value}\n</body>";
}
values.add(groupedElement);
I don't know why you use the '\n' but you can replace it with the break line html tag if it suits you

How to write a Reg Expression of a string that contains two tilde ~ , or two underscore

Asterisk is working well. But I haven't figured out how to write an expression for underscore and tilde... I have tried multiple combinations. Please ignore what you see on the code below.
else if (message.text.contains("_")) {
message.text.trim().splitMapJoin(RegExp(r'/_(._?)_/'), onMatch: (m) {
textParts.add(TextSpan(
text: m.group(1), style: TextStyle(fontStyle: FontStyle.italic)));
return '';
}, onNonMatch: (String text) {
textParts.add(TextSpan(text: text));
return ' ';
});
}
else if (message.text.contains("*")) {
message.text.trim().splitMapJoin(RegExp(r'\*(.*?)\*'), onMatch: (m) {
textParts.add(TextSpan(
text: m.group(1), style: TextStyle(fontWeight: FontWeight.bold)));
return '';
}, onNonMatch: (String text) {
textParts.add(TextSpan(text: text));
return ' ';
});
}
else if (message.text.contains("~")) {
message.text.trim().splitMapJoin(RegExp(r'\~(.~?)\~'), onMatch: (m) {
textParts.add(TextSpan(
text: m.group(1), style: TextStyle(decoration: TextDecoration.lineThrough)));
return '';
}, onNonMatch: (String text) {
textParts.add(TextSpan(text: text));
return ' ';
});
}
this is a function that returns a TextSpan based on input String:
TextSpan rich(String input) {
final styles = {
'_': const TextStyle(fontStyle: FontStyle.italic),
'*': const TextStyle(fontWeight: FontWeight.bold),
'~': const TextStyle(decoration: TextDecoration.lineThrough),
};
final spans = <TextSpan>[];
input.trim().splitMapJoin(RegExp(r'([_*~])(.*?)\1'), onMatch: (m) {
spans.add(TextSpan(text: m.group(2), style: styles[m.group(1)]));
return '';
}, onNonMatch: (String text) {
spans.add(TextSpan(text: text));
return '';
});
return TextSpan(style: const TextStyle(fontSize: 24), children: spans);
}
you can test it by calling:
final span = rich('some _italic_ word, some in *bold* and ~lineThrough~ works too');
EDIT
if you need multi styles like _*foo*_ for both italic and bold styles, you could do it by recursive calling rich() function:
TextSpan rich(String input, {TextStyle? style}) {
const styles = {
'_': TextStyle(fontStyle: FontStyle.italic),
'*': TextStyle(fontWeight: FontWeight.bold),
'~': TextStyle(decoration: TextDecoration.lineThrough),
};
final spans = <TextSpan>[];
final pattern = RegExp(r'([_*~])(.*?)\1');
input.trim().splitMapJoin(pattern, onMatch: (m) {
final input = m.group(2)!;
final style = styles[m.group(1)];
spans.add(
pattern.hasMatch(input)?
rich(input, style: style) : TextSpan(text: input, style: style)
);
return '';
}, onNonMatch: (String text) {
spans.add(TextSpan(text: text));
return '';
});
// print('input: "$input", spans: $spans');
return TextSpan(style: style, children: spans);
}
you can test it with:
final span = rich('''
⦁ nested multistyle:
some _italic words that can be *bold* too_
or _~italic & lineThrough~_
or even _*~3 in 1~*_
⦁ _italic_ style
⦁ *bold* style
⦁ ~lineThrough~ style''',
// style: is optional here
style: TextStyle(fontSize: 20, color: Colors.blue[900])
);
// and now you can use span in
// Text.rich(span)
EDIT2
for monospace code (```some code here```) you can use:
TextSpan rich(String input, {TextStyle? style}) {
const styles = {
'_': TextStyle(fontStyle: FontStyle.italic),
'*': TextStyle(fontWeight: FontWeight.bold),
'~': TextStyle(decoration: TextDecoration.lineThrough),
'```': TextStyle(fontFamily: 'monospace', color: Colors.black87),
};
final spans = <TextSpan>[];
final pattern = RegExp(r'([_*~]|`{3})(.*?)\1');
input.trim().splitMapJoin(pattern, onMatch: (m) {
final input = m.group(2)!;
final style = styles[m.group(1)];
spans.add(
pattern.hasMatch(input)?
rich(input, style: style) : TextSpan(text: input, style: style)
);
return '';
}, onNonMatch: (String text) {
spans.add(TextSpan(text: text));
return '';
});
// print('input: "$input", spans: $spans');
return TextSpan(style: style, children: spans);
}
the sample usage:
final span = rich('''
⦁ nested multistyle:
some _italic words that can be *bold* too_
or _~italic & lineThrough~_
or ```monospaced code with *bold* word```
or even _*~3 in 1~*_
⦁ _italic_ style
⦁ *bold* style
⦁ ~lineThrough~ style
⦁ ```code``` style''',
style: TextStyle(fontSize: 20, color: Colors.blue[900])
);
return Text.rich(span);
Here is a regex that will have a match if you either have _ some text _, *some text* or ~some text~, hopefully that solves your issue:
([_~*])(.*?)\1
of course, it also matches __, ~~ and **
Here is the Debuggex Demo
Example of usage:
void main() {
String txt = '_some text_ some *more* text, and some ~final~ text';
RegExp regex = RegExp('([_~*])(.*?)\\1');
if (regex.hasMatch(txt)) {
for (var match in regex.allMatches(txt))
{
String text = match.group(2)!;
String delimiter = match.group(1)!;
switch (delimiter) {
case '_':
print('underscore');
break;
case '~':
print('tilde');
break;
case '*':
print('star');
break;
}
print(text);
}
}
}
the above will print
underscore
some text
star
more
tilde
final
Edit 2:
User pskink's answer has a better example of usage for this regex

InitialValue isn't working properly in Multi-Select package Flutter

so I am using MultiSelectBottomSheetField in this package. I posted on their github as well as an issue but it seems fairly inactive so i came here looking for help.
And I am having some issues with the initialValue parameter for it. So at the moment, I have data saved in firestore as a string but its in the format of a list. And what i was trying to do was get the string data from firestore -> convert to a list with the respective class -> and then show as initial value in the above package/widget. But whats happening is that the initial value isnt showing, even though the value is not empty.
So for context this is how I change to list from firestore string:
List<Skill?> skillList = [];
void changeSkillToList(String? stringList) {
int indexOfOpenBracket = stringList!.indexOf("[");
int indexOfLastBracket = stringList.lastIndexOf("]");
var noBracketString =
stringList.substring(indexOfOpenBracket + 1, indexOfLastBracket);
var list = noBracketString.split(", ");
for (var i = 0; i < list.length; i++) {
skillList.add(Skill(id: 1, name: list[i].toString()));
}
}
this is how i use the acc widget:
final _skillItems =
skill.map((skill) => MultiSelectItem<Skill>(skill, skill.name)).toList();
MultiSelectBottomSheetField<Skill?>(
selectedColor: Color(0xFF5DB075),
selectedItemsTextStyle:
TextStyle(color: Colors.white),
initialChildSize: 0.4,
decoration: BoxDecoration(),
listType: MultiSelectListType.CHIP,
initialValue: skillList,
searchable: true,
items: _skillItems,
buttonText: Text("Select your skills...",
style: GoogleFonts.inter(
color: Color(0xFFBDBDBD),
fontSize: 16)),
onConfirm: (values) {
context
.read(pharmacistSignUpProvider.notifier)
.changeSkillList(values);
},
chipDisplay: MultiSelectChipDisplay(
items: context
.read(pharmacistSignUpProvider.notifier)
.skillList
?.map((e) =>
MultiSelectItem(e, e.toString()))
.toList(),
chipColor: Color(0xFF5DB075),
onTap: (value) {
context
.read(
pharmacistSignUpProvider.notifier)
.skillList
?.remove(value);
return context
.read(
pharmacistSignUpProvider.notifier)
.skillList;
},
textStyle: TextStyle(color: Colors.white),
),
),
And this is my initState:
List<Skill?> skillList = [];
#override
void initState() {
skillList = changeSkillToList(context
.read(pharmacistMainProvider.notifier)
.userDataMap?["knownSkills"]);
print(skillList);
super.initState();
}
If someone could help me out, it would be very appreciated. Let me know if you guys have any questions
Thanks!!
I get some problem and I fix it by adding the == operator to my entity in your case skill
#override
bool operator ==(Object other) {
return other is Skill && this.id == other.id;
}
inside your Skill class

How to change text color according to valid(#mentions) and all hashtags?

I wanted to display hashtags and valid mentions with different colors in the text.
I got helped with this code which works only for hashtags
RichText _convertHashtag(String text) {
List<String> split = text.split(RegExp("#"));
List<String> hashtags = split.getRange(1, split.length).fold([], (t, e) {
var texts = e.split(" ");
if (texts.length > 1) {
return List.from(t)
..addAll(["#${texts.first}", "${e.substring(texts.first.length)}"]);
}
return List.from(t)..add("#${texts.first}");
});
return RichText(
text: TextSpan(
children: [TextSpan(text: split.first)]..addAll(hashtags
.map((text) => text.contains("#")
? TextSpan(text: text, style: TextStyle(color: Colors.blue))
: TextSpan(text: text))
.toList()),
),
);
}
I modified it like:
List valid_mentions = ['#mention1', '#mention2'];//these are the valid mention
RichText _convertHashtag(String text) {
List<String> split = text.split(RegExp("#|#"));
List<String> hashtags = split.getRange(1, split.length).fold([], (t, e) {
var texts = e.split(" ");
//here adding `#` sign and `#` sign to the given texts and storing them in the `hashtags` list
if (texts.length > 1) {
if (valid_mentions.contains(texts.first))
return List.from(t)
..addAll(["#${texts.first}", "${e.substring(texts.first.length)}"]);
else if (text.contains('#${texts.first}')) {
return List.from(t)
..addAll(["#${texts.first}", "${e.substring(texts.first.length)}"]);
} else
return List.from(t)
..addAll(["#${texts.first}", "${e.substring(texts.first.length)}"]);
} else {
if (valid_mentions.contains(texts.first))
return List.from(t)..addAll(["#${texts.first}"]);
else if (text.contains('#${texts.first}')) {
return List.from(t)..addAll(["#${texts.first}"]);
} else
return List.from(t)..addAll(["#${texts.first}"]);
}
});
return RichText(
text: TextSpan(
children: [TextSpan(text: split.first)]..addAll(hashtags.map((text) {
return text.contains("#")
? valid_mentions.contains(text)
? //checking if the mention is valid
TextSpan(
text: text,
recognizer: TapGestureRecognizer()
..onTap = () {
print(text);
},
style: TextStyle(color: Colors.blue))
: TextSpan(
text: text,
)
: text.contains("#")
? TextSpan(
text: text,
recognizer: TapGestureRecognizer()
..onTap = () {
print(text);
},
style: TextStyle(color: Colors.blue))
: TextSpan(
text: text,
);
}).toList()),
),
);
}
I am able to make the required changes, but i belive its not an optimized way and there is a lot of boiler plate code. How can i optimize it?
input:"I love #flutter #android #mention1 #mention2 #mention3 "
output:"I love #flutter #android #mention1 #mention2 #mention3 "
here #mention3 is not hyperlinked because its not an valid mention.
From what I've understood, you want to highlight hashtags and mentions (pre stored in a list) in a String.
Let me break this into 2 parts, the first would be to extract the hashtags & mentions from the text.
List<String> getAllHashtags(String text) {
final regexp = RegExp(r'\#[a-zA-Z0-9]+\b()');
List<String> hashtags = [];
regexp.allMatches(text).forEach((element) {
if (element.group(0) != null) {
hashtags.add(element.group(0).toString());
}
});
return hashtags;
}
List<String> getAllMentions(String text) {
final regexp = RegExp(r'\#[a-zA-Z0-9]+\b()');
List<String> mentions = [];
regexp.allMatches(text).forEach((element) {
if (element.group(0) != null) {
mentions.add(element.group(0).toString());
}
});
return mentions;
}
The above code snippet will successfully extract hashtags & mentions from the given sentence and return it as a list.
The next step would be to build the RichText with the different TextSpans.
RichText buildHighlightedText(String text) {
// clean the text
text = cleanText(text);
List<String> validMentions = ["#mention1", "#mention2"];
List<String> hashtags = getAllHashtags(text);
List<String> mentions = getAllMentions(text);
List<TextSpan> textSpans = [];
text.split(" ").forEach((value) {
if (hashtags.contains(value)) {
textSpans.add(TextSpan(
text: '$value ',
style: TextStyle(color: Colors.amber, fontWeight: FontWeight.bold),
));
} else if (mentions.contains(value) && validMentions.contains(value)) {
textSpans.add(TextSpan(
text: '$value ',
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
));
} else {
textSpans.add(TextSpan(text: '$value '));
}
});
return RichText(text: TextSpan(children: textSpans));
}
The text has been split by empty spaces, and filters hashtags/mentions and returns differently styled TextSpans for each. This is a more concise and cleaner way of doing what you're looking for.
Here's an example:
Edit:
In order to parse hashtags without spaces, we need to add each instance with a space in front.
String cleanText(String text) {
text = text.replaceAllMapped(
RegExp(r'\w#+'), (Match m) => "${m[0]?.split('').join(" ")}");
text = text.replaceAllMapped(
RegExp(r'\w#+'), (Match m) => "${m[0]?.split('').join(" ")}");
return text;
}

Detect # in text and show it as link (Flutter)

I wonder how to create a recognizer to detect # in the text & change its color to something like a blue one and also add onTap functionality to it :) like this picture.
I think this should work for anything like #someone:
You need to import this: import 'package:flutter/gestures.dart';
This is your input String: String input = "This is a long text but in between there should be this: #someone The text continues here, but there is #another.";
This is the widget I made for you : textSplitter(input);
Widget textSplitter(String input) {
List<TextSpan> textSpans = [];
for (var i = 0; i <= '#'.allMatches(input).length; i++) {
String part = input.substring(0, input.indexOf('#')==-1? input.length : input.indexOf('#'));
textSpans.add(TextSpan(text: part, style: TextStyle(color: Colors.white)));
input = input.substring(input.indexOf('#'));
String clickable = input.substring(0, input.indexOf(' ')==-1? input.length : input.indexOf(' '));
textSpans.add(
TextSpan(
text: clickable,
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
recognizer: new TapGestureRecognizer()
..onTap = () {
//do stuff here with string clickable
print(clickable);
},
),
);
input = input.substring(input.indexOf(' ')==-1? input.length : input.indexOf(' '));
}
return RichText(
text: TextSpan(
children: textSpans,
),
);
}
The result should look like this:
Note that anything after the # is included until a space.