feat: custom emoji in chat

This commit is contained in:
2025-05-15 11:34:21 +01:00
parent be66446e85
commit 4c6d5b995f
3 changed files with 61 additions and 29 deletions

View File

@ -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:flutter/material.dart';
import 'package:ndk/ndk.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/main.dart';
import 'package:zap_stream_flutter/rx_filter.dart'; import 'package:zap_stream_flutter/rx_filter.dart';
import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/utils.dart'; import 'package:zap_stream_flutter/utils.dart';
import 'package:zap_stream_flutter/widgets/avatar.dart'; import 'package:zap_stream_flutter/widgets/avatar.dart';
import 'package:zap_stream_flutter/widgets/chat_modal.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/nostr_text.dart';
import 'package:zap_stream_flutter/widgets/profile.dart'; import 'package:zap_stream_flutter/widgets/profile.dart';
@ -132,33 +130,11 @@ class ChatReactions extends StatelessWidget {
borderRadius: DEFAULT_BR, borderRadius: DEFAULT_BR,
), ),
child: Center( 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);
}
}
} }

View File

@ -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<List<String>> 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);
}
}
}

View File

@ -6,6 +6,7 @@ import 'package:ndk/shared/nips/nip19/nip19.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/utils.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'; import 'package:zap_stream_flutter/widgets/profile.dart';
class NoteText extends StatelessWidget { class NoteText extends StatelessWidget {
@ -30,12 +31,12 @@ List<InlineSpan> textToSpans(
List<List<String>> tags, List<List<String>> tags,
String pubkey, String pubkey,
) { ) {
return _buildContentSpans(content); return _buildContentSpans(content, tags);
} }
/// Content parser from camelus /// 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 /// https://github.com/leo-lox/camelus/blob/f58455a0ac07fcc780bdc69b8f4544fd5ea4a46d/lib/presentation_layer/components/note_card/note_card_build_split_content.dart#L262
List<InlineSpan> _buildContentSpans(String content) { List<InlineSpan> _buildContentSpans(String content, List<List<String>> tags) {
List<InlineSpan> spans = []; List<InlineSpan> spans = [];
RegExp exp = RegExp( RegExp exp = RegExp(
r'nostr:(nprofile|npub)[a-zA-Z0-9]+|' r'nostr:(nprofile|npub)[a-zA-Z0-9]+|'
@ -62,7 +63,23 @@ List<InlineSpan> _buildContentSpans(String content) {
return ''; return '';
}, },
onNonMatch: (String text) { onNonMatch: (String 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)); spans.add(TextSpan(text: text));
}
return ''; return '';
}, },
); );