From 4c6d5b995fcbaa937930ad6e466563880efda12a Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 15 May 2025 11:34:21 +0100 Subject: [PATCH] feat: custom emoji in chat --- lib/widgets/chat_message.dart | 28 ++----------------------- lib/widgets/custom_emoji.dart | 39 +++++++++++++++++++++++++++++++++++ lib/widgets/nostr_text.dart | 23 ++++++++++++++++++--- 3 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 lib/widgets/custom_emoji.dart diff --git a/lib/widgets/chat_message.dart b/lib/widgets/chat_message.dart index c08ee8d..bdf6bfa 100644 --- a/lib/widgets/chat_message.dart +++ b/lib/widgets/chat_message.dart @@ -1,14 +1,12 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:ndk/ndk.dart'; -import 'package:zap_stream_flutter/imgproxy.dart'; import 'package:zap_stream_flutter/main.dart'; import 'package:zap_stream_flutter/rx_filter.dart'; import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/utils.dart'; import 'package:zap_stream_flutter/widgets/avatar.dart'; import 'package:zap_stream_flutter/widgets/chat_modal.dart'; +import 'package:zap_stream_flutter/widgets/custom_emoji.dart'; import 'package:zap_stream_flutter/widgets/nostr_text.dart'; import 'package:zap_stream_flutter/widgets/profile.dart'; @@ -132,33 +130,11 @@ class ChatReactions extends StatelessWidget { borderRadius: DEFAULT_BR, ), child: Center( - child: _rectionContent(context, v.key, v.value.first), + child: CustomEmoji(emoji: v.key, tags: v.value.first.tags), ), ), ), ], ); } - - Widget _rectionContent( - BuildContext context, - String reaction, - Nip01Event event, - ) { - final customEmoji = - event.tags.firstWhereOrNull( - (t) => - t[0] == "emoji" && - t[1] == reaction.substring(1, reaction.length - 1), - )?[2]; - if (customEmoji != null) { - return CachedNetworkImage( - imageUrl: proxyImg(context, customEmoji), - height: 15, - width: 15, - ); - } else { - return Text(reaction); - } - } } diff --git a/lib/widgets/custom_emoji.dart b/lib/widgets/custom_emoji.dart new file mode 100644 index 0000000..3828032 --- /dev/null +++ b/lib/widgets/custom_emoji.dart @@ -0,0 +1,39 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:zap_stream_flutter/imgproxy.dart'; + +class CustomEmoji extends StatelessWidget { + final List> tags; + final String emoji; + final double? size; + + const CustomEmoji({ + super.key, + required this.tags, + required this.emoji, + this.size, + }); + + @override + Widget build(BuildContext context) { + final cleanedEmojiName = + emoji.startsWith(":") && emoji.endsWith(":") + ? emoji.substring(1, emoji.length - 1) + : emoji; + + final customEmoji = + tags.firstWhereOrNull( + (t) => t[0] == "emoji" && t[1] == cleanedEmojiName, + )?[2]; + if (customEmoji != null) { + return CachedNetworkImage( + imageUrl: proxyImg(context, customEmoji), + height: size ?? 16, + width: size ?? 16, + ); + } else { + return Text(emoji); + } + } +} diff --git a/lib/widgets/nostr_text.dart b/lib/widgets/nostr_text.dart index 724a15f..bc00b87 100644 --- a/lib/widgets/nostr_text.dart +++ b/lib/widgets/nostr_text.dart @@ -6,6 +6,7 @@ import 'package:ndk/shared/nips/nip19/nip19.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/utils.dart'; +import 'package:zap_stream_flutter/widgets/custom_emoji.dart'; import 'package:zap_stream_flutter/widgets/profile.dart'; class NoteText extends StatelessWidget { @@ -30,12 +31,12 @@ List textToSpans( List> tags, String pubkey, ) { - return _buildContentSpans(content); + return _buildContentSpans(content, tags); } /// Content parser from camelus /// https://github.com/leo-lox/camelus/blob/f58455a0ac07fcc780bdc69b8f4544fd5ea4a46d/lib/presentation_layer/components/note_card/note_card_build_split_content.dart#L262 -List _buildContentSpans(String content) { +List _buildContentSpans(String content, List> tags) { List spans = []; RegExp exp = RegExp( r'nostr:(nprofile|npub)[a-zA-Z0-9]+|' @@ -62,7 +63,23 @@ List _buildContentSpans(String content) { return ''; }, onNonMatch: (String text) { - spans.add(TextSpan(text: text)); + final textTrim = text.trim(); + if (textTrim.startsWith(":") && + textTrim.endsWith(":") && + tags.any( + (t) => + t[0] == "emoji" && + t[1] == textTrim.substring(1, textTrim.length - 1), + )) { + spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: CustomEmoji(emoji: textTrim, tags: tags, size: 24), + ), + ); + } else { + spans.add(TextSpan(text: text)); + } return ''; }, );