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.
Related
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
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
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;
}
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:
I am trying to make a wats-app like link preview feature , it has two parts ,
Detect URL from text field
Showing preview of that URL
Part 2 has so many plugins to show the preview , but I am stuck with part 1 , how to detect and parse a URL on user typing on textfield .
Also is there a plugin serving both ?
Detect URLs in String/Paragraph and convert them in links in DART:
//
String convertStringToLink(String textData) {
//
final urlRegExp = new RegExp(
r"((https?:www\.)|(https?:\/\/)|(www\.))[-a-zA-Z0-9#:%._\+~#=]{1,256}\.[a-zA-Z0-9]{1,6}(\/[-a-zA-Z0-9()#:%_\+.~#?&\/=]*)?");
final urlMatches = urlRegExp.allMatches(textData);
List<String> urls = urlMatches.map(
(urlMatch) => textData.substring(urlMatch.start, urlMatch.end))
.toList();
List linksString = [];
urls.forEach((String linkText){
linksString.add(linkText);
});
if (linksString.length > 0) {
linksString.forEach((linkTextData) {
textData = textData.replaceAll(
linkTextData,
'<a href="' +
linkTextData +
'" target="_blank">' +
linkTextData +
'</a>');
});
}
return textData;
}
Demo and how to call
String text = "This is my website url: https://github.com/ Google search using: www.google.com, Flutter url: http://example.com/method?param=flutter stackoverflow website url is https://www.stackoverflow.com is greatest website and also check this hashed url https://support.google.com/firebase?authuser=0#topic=6399725";
print(convertStringToLink(text));
Output:
This is my website url: https://github.com/ Google search using: www.google.com, Flutter url: http://example.com/method?param=flutter stackoverflow website url is https://www.stackoverflow.com is greatest website and also check this hashed url https://support.google.com/firebase?authuser=0#topic=6399725
It worked for me, will definitely help my friends :)
I hope that this could help others, talking about step 1:
To detect a URL from the text view, I do the following (taking into consideration that my use case is a chat message in which in the middle of the text could be 1 or several links)
First, having a function that given a String, identify that are URLs:
bool hasURLs(String text) {
const pattern =
r"(https?:\/\/(www.)?|www.)([\w-]+.([\w-]+.)?[\w]+)([\w./?=%-]*)";
final regExp = RegExp(pattern);
return regExp.hasMatch(text);
}
Then a logic to display the text message with a link or without links:
final hasUrls = formatter.hasURLs(stringMessage);
In a widget:
return hasUrls
? UrlMessage(
textContent: messageContents,
textColor: textColor,
isMyMessage: isMyMessage,
)
: Text(
messageContents,
style: TextStyle(color: textColor, fontSize: 13),
);
For UrlMessage widget the code as follows:
class UrlMessage extends StatefulWidget {
const UrlMessage({
Key? key,
required this.textContent,
required this.textColor,
required this.isMyMessage,
}) : super(key: key);
final String textContent;
final Color textColor;
final bool isMyMessage;
#override
State<UrlMessage> createState() => _UrlMessageState();
}
class _UrlMessageState extends State<UrlMessage> {
final formatter = Formatter();
#override
Widget build(BuildContext context) {
final text = widget.textContent;
final textColor = widget.textColor;
final isMyMessage = widget.isMyMessage;
final linkTextStyle = TextStyle(
color: isMyMessage ? Colors.blueGrey : Colors.blue,
fontSize: 13,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
);
return RichText(
// map each word of the message, ask if it is a URL then set it with each
// TextSpan as a link or not. If it's a link use launchUrl from `url_launcher`
// package to open it
text: TextSpan(
children: text.split(' ').map((word) {
// Check for URLs
if (formatter.hasURLs(word)) {
return TextSpan(
text: word,
style: linkTextStyle,
recognizer: TapGestureRecognizer()
..onTap = () {
// Handle link - here we use `url_launcher`
launchUrl(Uri.parse(word));
},
);
} else {
return TextSpan(
text: '$word ',
style: TextStyle(color: textColor, fontSize: 13),
);
}
}).toList(),
),
);
}
}
Regarding step 2, there are several options to work with the preview, in our case Any Link Preview does what we need
You could try Uri.parse(). Do check the link https://www.geeksforgeeks.org/dart-uris/
Get the value from the textfield Using onChanged function and controller
https://medium.com/flutter-community/a-deep-dive-into-flutter-textfields-f0e676aaab7a