Skip to content

Commit 721c58a

Browse files
content: Parse emojiUnicode in UnicodeEmojiNode
1 parent 91f8b94 commit 721c58a

File tree

3 files changed

+45
-26
lines changed

3 files changed

+45
-26
lines changed

lib/model/content.dart

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,22 +438,22 @@ abstract class EmojiNode extends InlineContentNode {
438438
}
439439

440440
class UnicodeEmojiNode extends EmojiNode {
441-
const UnicodeEmojiNode({super.debugHtmlNode, required this.text});
441+
const UnicodeEmojiNode({super.debugHtmlNode, required this.unicode});
442442

443-
final String text;
443+
final String unicode;
444444

445445
@override
446446
bool operator ==(Object other) {
447-
return other is UnicodeEmojiNode && other.text == text;
447+
return other is UnicodeEmojiNode && other.unicode == unicode;
448448
}
449449

450450
@override
451-
int get hashCode => Object.hash('UnicodeEmojiNode', text);
451+
int get hashCode => Object.hash('UnicodeEmojiNode', unicode);
452452

453453
@override
454454
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
455455
super.debugFillProperties(properties);
456-
properties.add(StringProperty('text', text));
456+
properties.add(StringProperty('unicode', unicode));
457457
}
458458
}
459459

@@ -568,7 +568,19 @@ class _ZulipContentParser {
568568
&& classes.length == 2
569569
&& classes.contains('emoji')
570570
&& classes.every(_emojiClassRegexp.hasMatch)) {
571-
return UnicodeEmojiNode(text: element.text, debugHtmlNode: debugHtmlNode);
571+
// TODO: Check if fonts contain the glyph for the specific unicodes.
572+
// Either by parsing the font and generating a list of
573+
// unicodes that have a corresponding glyph.
574+
// Or by checking if an emoji is present in an emoji set,
575+
// eg. unicode v15 set of emojis.
576+
577+
final className = classes
578+
.firstWhere((className) => className.startsWith('emoji-'), orElse: () => '')
579+
.replaceFirst('emoji-', '');
580+
if (className.isEmpty) return unimplemented();
581+
final unicode = tryParseEmojiCodeToUnicode(className);
582+
if (unicode == null) return unimplemented();
583+
return UnicodeEmojiNode(unicode: unicode, debugHtmlNode: debugHtmlNode);
572584
}
573585

574586
if (localName == 'img'
@@ -627,6 +639,27 @@ class _ZulipContentParser {
627639
return ListNode(listStyle!, items, debugHtmlNode: debugHtmlNode);
628640
}
629641

642+
// Ported from https://github.com/zulip/zulip-mobile/blob/c979530d6804db33310ed7d14a4ac62017432944/src/emoji/data.js#L108-L112
643+
//
644+
// Which was in turn ported from https://github.com/zulip/zulip/blob/63c9296d5339517450f79f176dc02d77b08020c8/zerver/models.py#L3235-L3242
645+
// and that describes the encoding as follows:
646+
//
647+
// > * For Unicode emoji, [emoji_code is] a dash-separated hex encoding of
648+
// > the sequence of Unicode codepoints that define this emoji in the
649+
// > Unicode specification. For examples, see "non_qualified" or
650+
// > "unified" in the following data, with "non_qualified" taking
651+
// > precedence when both present:
652+
// > https://raw.githubusercontent.com/iamcal/emoji-data/master/emoji_pretty.json
653+
String? tryParseEmojiCodeToUnicode(String code) {
654+
try {
655+
return String.fromCharCodes(code.split('-').map((hex) => int.parse(hex, radix: 16)));
656+
} on FormatException { // thrown by `int.parse`
657+
return null;
658+
} on ArgumentError { // thrown by `String.fromCharCodes`
659+
return null;
660+
}
661+
}
662+
630663
BlockContentNode parseCodeBlock(dom.Element divElement) {
631664
assert(_debugParserContext == _ParserContext.block);
632665
final mainElement = () {

lib/widgets/content.dart

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,7 @@ class _InlineContentBuilder {
449449
return WidgetSpan(alignment: PlaceholderAlignment.middle,
450450
child: UserMention(node: node));
451451
} else if (node is UnicodeEmojiNode) {
452-
return WidgetSpan(alignment: PlaceholderAlignment.middle,
453-
child: MessageUnicodeEmoji(node: node));
452+
return TextSpan(text: node.unicode, recognizer: _recognizer);
454453
} else if (node is ImageEmojiNode) {
455454
return WidgetSpan(alignment: PlaceholderAlignment.middle,
456455
child: MessageImageEmoji(node: node));
@@ -609,23 +608,6 @@ class UserMention extends StatelessWidget {
609608
// borderRadius: BorderRadius.all(Radius.circular(3))));
610609
}
611610

612-
class MessageUnicodeEmoji extends StatelessWidget {
613-
const MessageUnicodeEmoji({super.key, required this.node});
614-
615-
final UnicodeEmojiNode node;
616-
617-
@override
618-
Widget build(BuildContext context) {
619-
// TODO(#58) get spritesheet and show actual emoji glyph
620-
final text = node.text;
621-
return Container(
622-
padding: const EdgeInsets.all(2),
623-
decoration: BoxDecoration(
624-
color: Colors.white, border: Border.all(color: Colors.purple)),
625-
child: Text(text));
626-
}
627-
}
628-
629611
class MessageImageEmoji extends StatelessWidget {
630612
const MessageImageEmoji({super.key, required this.node});
631613

test/model/content_test.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ void main() {
143143
testParseInline('parse Unicode emoji',
144144
// ":thumbs_up:"
145145
'<p><span aria-label="thumbs up" class="emoji emoji-1f44d" role="img" title="thumbs up">:thumbs_up:</span></p>',
146-
const UnicodeEmojiNode(text: ':thumbs_up:'));
146+
const UnicodeEmojiNode(unicode: '\u{1f44d}'));
147+
148+
testParseInline('parse Unicode emoji, invalid unicode',
149+
'<p><span aria-label="thumbs up" class="emoji emoji-abcdef" role="img" title="thumbs up">:thumbs_up:</span></p>',
150+
inlineUnimplemented('<span aria-label="thumbs up" class="emoji emoji-abcdef" role="img" title="thumbs up">:thumbs_up:</span>'));
147151

148152
testParseInline('parse custom emoji',
149153
// ":flutter:"

0 commit comments

Comments
 (0)