Android has horrid emojis. Can I use Apple's emoji font (like Telegram does and WhatsApp used to) in Flutter for Android?
Edit: My first guess would be to add a font, but as I do not want to change all other fonts I am not sure if this is possible.
There is currently no way to define a custom font in Flutter just for emojis (or just for specified characters). There's an open Github issue about this.
There is however a trick to mix your font and another one for your emojis in a Text widget. I found an answer here to do that.
Here are the steps you can follow :
You need to choose the emoji font you want to use. My favorite one is this Joypixels :
https://www.joypixels.com/fonts
Download the font .ttf and add it to your project, following the documentation :
https://flutter.dev/docs/cookbook/design/fonts
Add this code to your project :
class TextWithEmojis extends StatelessWidget {
const TextWithEmojis({
Key? key,
required this.text,
required this.fontSize,
required this.color,
}) : super(key: key);
final String text;
final double fontSize;
final Color color;
#override
Widget build(BuildContext context) {
return RichText(
text: _buildText(),
);
}
TextSpan _buildText() {
final children = <TextSpan>[];
final runes = text.runes;
for (int i = 0; i < runes.length; /* empty */) {
int current = runes.elementAt(i);
// we assume that everything that is not
// in Extended-ASCII set is an emoji...
final isEmoji = current > 255;
final shouldBreak = isEmoji ? (x) => x <= 255 : (x) => x > 255;
final chunk = <int>[];
while (!shouldBreak(current)) {
chunk.add(current);
if (++i >= runes.length) break;
current = runes.elementAt(i);
}
children.add(
TextSpan(
text: String.fromCharCodes(chunk),
style: TextStyle(
fontSize: fontSize,
color: color,
fontFamily: isEmoji ? 'Joypixels' : null,
),
),
);
}
return TextSpan(children: children);
}
}
You can now use TextWithEmojis whenever you need :
TextWithEmojis(
text: "Hello 👋 😄",
fontSize: 12,
color: Colors.white
)
Hot reloading or Hot restaring won't make the Text go to the incorrect state. Only running the app again.
Related
I hope somebody can explain me some kind of solution:)
I've been working on app UI using Flutter and there are 2 types of a button in that app design on Figma, each has its accent (there're 3 for now), which just define the button's heigth.
Those 2 types of button and accents
So those buttons have different emphasis over the app and I need to somehow manage it following the best coding practices.
Well for now I simply created enum, which contains accent variation:
enum ButtonAccent {
primary,
secondary,
tertiary,
}
class FilledButton extends StatelessWidget {
final String text;
final IconData icon;
final Color backgroundColor;
final Color foregroundColor;
// defines a button height following Figma design. By default 'buttonAccent = ButtonAccent.tertiary', which sets 'heigth = 46'
final ButtonAccent buttonAccent;
final VoidCallback onPressed;
const FilledButton(
{super.key,
required this.text,
required this.icon,
this.backgroundColor = ColorConstants.kCallToAction,
this.foregroundColor = ColorConstants.kText,
this.buttonAccent = ButtonAccent.tertiary,
required this.onPressed});
// method that checks accent
double _buttonHeigthFromAccent() => buttonAccent == ButtonAccent.primary
? 72.0
: buttonAccent == ButtonAccent.secondary
? 60.0
: buttonAccent == ButtonAccent.tertiary
? 48.0
: throw Exception('Wrong ButtonAccent value');
#override
Widget build(BuildContext context) {
return ElevatedButton.icon(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, _buttonHeigthFromAccent()),
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
textStyle: const TextStyle(color: ColorConstants.kText),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0))),
icon: Icon(icon),
label: Text(text),
);
}
}
Then my FilledButton contains method that checks the accents passed via constuctor and returning the proper heigth.
But! There're plenty of cons in my opinion:
if we consider to change not just height of a button but style in general (color, shape etc), it will lead us to overwrite the whole button implementation and ButtonAccent enum;
not sure that all principles of SOLID are met;
I put _buttonHeigthFromAccent() in both CustomOutlinedButton and FilledButton (added the code of FilledButton only as it doesn't differ much) which is bad idea as well;
I think it would be better to create an abstract class (interface) so I could implement it for my needs.
However I am not sure about it, it can be just extra, pointless work
You can use enhanced enum, it is comes by default from v2.17.0
enum ButtonAccent {
primary(72.0),
secondary(60.0),
tertiary(48.0);
const ButtonAccent(this.size);
final double size;
}
And use the size like
minimumSize: Size(double.infinity, buttonAccent.size),
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.
I'm looking to implement dynamic text color with flutter-markdown using a custom syntax and parser. While the implementation below does successfully color text, I lose inner-text styling.
For example, the string
"not blue <span data-color='rgb(0,0,255)'>**blue text**</span> not blue"
shows up as
rather than showing up blue and bold.
While dynamic color isn't really the purpose of markdown, it would make my project considerably easier (I would use flutter-html, but the limits on selectable text make me hesitant)
My current implementation is as follows:
class FontColorSyntax extends md.InlineSyntax {
//Hideous => matches groups as follows: <span data-color="rgb(1,2,3)">4</span>
FontColorSyntax()
: super(
r'''<span(?:.*?)data-color=['"]rgb *\((?: *([0-9]{1,3}))[, ]+(?: *([0-9]{1,3}))[, ]+(?: *([0-9]{1,3}))[, ]*\)['"](?:.*?)>(.*?)</span>''');
#override
bool onMatch(md.InlineParser parse, Match match) {
md.Element colorTag = md.Element.text('fontcolor', match.group(4) ?? "matched text");
colorTag.attributes["fontColorRed"] = match.group(1) ?? "0";
colorTag.attributes["fontColorGreen"] = match.group(2) ?? "0";
colorTag.attributes["fontColorBlue"] = match.group(3) ?? "0";
parse.addNode(colorTag);
return true;
}
}
class FontColorBuilder extends MarkdownElementBuilder {
int getColor(md.Element el, String color) =>
int.tryParse(el.attributes["fontColor$color"] ?? "0") ?? 0;
#override
Widget visitElementAfter(element, style) {
final red = getColor(element, "Red");
final green = getColor(element, "Green");
final blue = getColor(element, "Blue");
return SelectableText.rich(
TextSpan(
text: element.textContent,
style: TextStyle(
color: Color.fromRGBO(red, green, blue, 1),
)),
);
}
I'm still quite new to flutter/dart, so please forgive (but feel free to point out) any oddities in my code.
Instead of returning a SelectableText returning another MarkdownBody widget should do the trick:
return MarkdownBody(
styleSheet: MarkdownStyleSheet.fromTheme(ThemeData(
textTheme:
const TextTheme(bodyText2: TextStyle(
color: Color.fromRGBO(red, green, blue, 1)
)))),
data: element.textContent);
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
I have created a CardText as a stateless widget and I will use it whenever I would be needing it. But I have a problem. As y'all can see, there are properties that I haven't marked as #required. What I want is these properties have a pre-defined value. Like, suppose the color property, it should be 0xFFFFFFFF until and unless I want somewhere to be as 0xFF000000. But these are final properties that can't be assigned on the basis of ??= method. Yes, I know, marking these properties as #required will require me to define each and every property whenever I call it. But having a pre-defined value will help me a lot to save time and a few lines of code.
Well, any expert out there, I don't know how to express the problem, so feel free to change the title. Thank you.
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class CardText extends StatelessWidget {
final String data;
final int color;
final int fontSize;
final FontWeight fontWeight;
const CardText(
this.data, {
this.color,
this.fontSize,
this.fontWeight,
});
#override
Widget build(BuildContext context) {
return Text(
data,
style: GoogleFonts.openSans(
textStyle: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
color: Color(color),
),
),
);
}
}
If your arguments are optional then you can give default it right away, like following
const CardText({
this.data,
this.color = 0xFFFFFFFF,
this.fontSize = 14,
this.fontWeight,
})
You can use the : colon syntax:
const CardText(
this.data, {
this.color,
this.fontSize,
this.fontWeight,
}) : color = 0xFFFFFFFF, data = "data"
The code after the colon will be executed before the code inside the curly brackets. From the linked question
The part after : is called "initializer list. It is a ,-separated list
of expressions that can access constructor parameters and can assign
to instance fields, even final instance fields. This is handy to
initialize final fields with calculated values.