From 01fa228e453d74cf5c20df6c526ec47bb0a94fdb Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 26 Oct 2025 03:04:02 +0800 Subject: [PATCH] :recycle: Refactor markdown sticker rendering --- lib/widgets/content/markdown.dart | 108 ++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/lib/widgets/content/markdown.dart b/lib/widgets/content/markdown.dart index cd5567b9..0108c3f8 100644 --- a/lib/widgets/content/markdown.dart +++ b/lib/widgets/content/markdown.dart @@ -47,7 +47,6 @@ class MarkdownTextContent extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final baseUrl = ref.watch(serverUrlProvider); final doesEnlargeSticker = useMemoized(() { // Check if content only contains one sticker by matching the sticker pattern final stickerPattern = RegExp(stickerRegex); @@ -88,6 +87,14 @@ class MarkdownTextContent extends HookConsumerWidget { onToggle: () => spoilerRevealed.value = !spoilerRevealed.value, ); + final baseUrl = ref.watch(serverUrlProvider); + final stickerGenerator = StickerGenerator( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + isEnlarged: doesEnlargeSticker, + baseUrl: baseUrl, + ); + return MarkdownBlock( data: content, selectable: isSelectable, @@ -186,28 +193,6 @@ class MarkdownTextContent extends HookConsumerWidget { ).clipRRect(all: 8), ), ); - case 'stickers': - final size = doesEnlargeSticker ? 96.0 : 24.0; - final stickerUri = - '$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open'; - return ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - ), - child: UniversalImage( - uri: stickerUri, - width: size, - height: size, - fit: BoxFit.contain, - noCacheOptimization: true, - ), - ), - ); } } final content = ClipRRect( @@ -228,7 +213,12 @@ class MarkdownTextContent extends HookConsumerWidget { generator: MarkdownTextContent.buildGenerator( isDark: isDark, linesMargin: linesMargin, - generators: [mentionGenerator, highlightGenerator, spoilerGenerator], + generators: [ + mentionGenerator, + highlightGenerator, + spoilerGenerator, + stickerGenerator, + ], ), ); } @@ -284,9 +274,8 @@ class _StickerInlineSyntax extends markdown.InlineSyntax { @override bool onMatch(markdown.InlineParser parser, Match match) { final placeholder = match[1]!; - final image = markdown.Element.text('img', '') - ..attributes['src'] = Uri.encodeFull('solian://stickers/$placeholder'); - parser.addNode(image); + final element = markdown.Element('sticker', [markdown.Text(placeholder)]); + parser.addNode(element); return true; } @@ -559,3 +548,68 @@ class SpoilerSpanNode extends SpanNode { ); } } + +class StickerGenerator extends SpanNodeGeneratorWithTag { + StickerGenerator({ + required Color backgroundColor, + required Color foregroundColor, + required bool isEnlarged, + required String baseUrl, + }) : super( + tag: 'sticker', + generator: ( + markdown.Element element, + MarkdownConfig config, + WidgetVisitor visitor, + ) { + return StickerSpanNode( + placeholder: element.textContent, + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + isEnlarged: isEnlarged, + baseUrl: baseUrl, + ); + }, + ); +} + +class StickerSpanNode extends SpanNode { + final String placeholder; + final Color backgroundColor; + final Color foregroundColor; + final bool isEnlarged; + final String baseUrl; + + StickerSpanNode({ + required this.placeholder, + required this.backgroundColor, + required this.foregroundColor, + required this.isEnlarged, + required this.baseUrl, + }); + + @override + InlineSpan build() { + final size = isEnlarged ? 96.0 : 24.0; + final stickerUri = '$baseUrl/sphere/stickers/lookup/$placeholder/open'; + return WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Container( + decoration: BoxDecoration( + color: backgroundColor.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: UniversalImage( + uri: stickerUri, + width: size, + height: size, + fit: BoxFit.contain, + noCacheOptimization: true, + ), + ), + ), + ); + } +}