From 2b80ebc2d02529cab0edcba07257e012902e40a5 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 16 Aug 2025 02:14:44 +0800 Subject: [PATCH] :bug: Fix markdown image in chat close #167 --- lib/models/embed.dart | 4 ++-- lib/models/embed.freezed.dart | 32 ++++++++++++++--------------- lib/models/embed.g.dart | 4 ++-- lib/widgets/content/embed/link.dart | 8 ++++---- lib/widgets/content/markdown.dart | 12 ++++++++--- lib/widgets/share/share_sheet.dart | 14 ++++++++----- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/lib/models/embed.dart b/lib/models/embed.dart index c694e06..05b727b 100644 --- a/lib/models/embed.dart +++ b/lib/models/embed.dart @@ -11,8 +11,8 @@ sealed class SnScrappedLink with _$SnScrappedLink { required String title, required String? description, required String? imageUrl, - required String faviconUrl, - required String siteName, + required String? faviconUrl, + required String? siteName, required String? contentType, required String? author, required DateTime? publishedDate, diff --git a/lib/models/embed.freezed.dart b/lib/models/embed.freezed.dart index 4af123f..d566bda 100644 --- a/lib/models/embed.freezed.dart +++ b/lib/models/embed.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$SnScrappedLink { - String get type; String get url; String get title; String? get description; String? get imageUrl; String get faviconUrl; String get siteName; String? get contentType; String? get author; DateTime? get publishedDate; + String get type; String get url; String get title; String? get description; String? get imageUrl; String? get faviconUrl; String? get siteName; String? get contentType; String? get author; DateTime? get publishedDate; /// Create a copy of SnScrappedLink /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -48,7 +48,7 @@ abstract mixin class $SnScrappedLinkCopyWith<$Res> { factory $SnScrappedLinkCopyWith(SnScrappedLink value, $Res Function(SnScrappedLink) _then) = _$SnScrappedLinkCopyWithImpl; @useResult $Res call({ - String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate + String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate }); @@ -65,16 +65,16 @@ class _$SnScrappedLinkCopyWithImpl<$Res> /// Create a copy of SnScrappedLink /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = freezed,Object? siteName = freezed,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { return _then(_self.copyWith( type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable -as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable -as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable -as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable +as String?,faviconUrl: freezed == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable +as String?,siteName: freezed == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable +as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable as DateTime?, @@ -159,7 +159,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _SnScrappedLink() when $default != null: return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _: @@ -180,7 +180,7 @@ return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUr /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate) $default,) {final _that = this; switch (_that) { case _SnScrappedLink(): return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);} @@ -197,7 +197,7 @@ return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUr /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate)? $default,) {final _that = this; switch (_that) { case _SnScrappedLink() when $default != null: return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _: @@ -220,8 +220,8 @@ class _SnScrappedLink implements SnScrappedLink { @override final String title; @override final String? description; @override final String? imageUrl; -@override final String faviconUrl; -@override final String siteName; +@override final String? faviconUrl; +@override final String? siteName; @override final String? contentType; @override final String? author; @override final DateTime? publishedDate; @@ -259,7 +259,7 @@ abstract mixin class _$SnScrappedLinkCopyWith<$Res> implements $SnScrappedLinkCo factory _$SnScrappedLinkCopyWith(_SnScrappedLink value, $Res Function(_SnScrappedLink) _then) = __$SnScrappedLinkCopyWithImpl; @override @useResult $Res call({ - String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate + String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate }); @@ -276,16 +276,16 @@ class __$SnScrappedLinkCopyWithImpl<$Res> /// Create a copy of SnScrappedLink /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = freezed,Object? siteName = freezed,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { return _then(_SnScrappedLink( type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable -as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable -as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable -as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable +as String?,faviconUrl: freezed == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable +as String?,siteName: freezed == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable +as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable as DateTime?, diff --git a/lib/models/embed.g.dart b/lib/models/embed.g.dart index 52e7c86..dc740e7 100644 --- a/lib/models/embed.g.dart +++ b/lib/models/embed.g.dart @@ -13,8 +13,8 @@ _SnScrappedLink _$SnScrappedLinkFromJson(Map json) => title: json['title'] as String, description: json['description'] as String?, imageUrl: json['image_url'] as String?, - faviconUrl: json['favicon_url'] as String, - siteName: json['site_name'] as String, + faviconUrl: json['favicon_url'] as String?, + siteName: json['site_name'] as String?, contentType: json['content_type'] as String?, author: json['author'] as String?, publishedDate: diff --git a/lib/widgets/content/embed/link.dart b/lib/widgets/content/embed/link.dart index 7a63df6..d1c483c 100644 --- a/lib/widgets/content/embed/link.dart +++ b/lib/widgets/content/embed/link.dart @@ -57,11 +57,11 @@ class EmbedLinkWidget extends StatelessWidget { Row( children: [ // Favicon - if (link.faviconUrl.isNotEmpty) ...[ + if (link.faviconUrl?.isNotEmpty ?? false) ...[ ClipRRect( borderRadius: BorderRadius.circular(4), child: UniversalImage( - uri: link.faviconUrl, + uri: link.faviconUrl!, width: 16, height: 16, fit: BoxFit.cover, @@ -80,8 +80,8 @@ class EmbedLinkWidget extends StatelessWidget { // Site name Expanded( child: Text( - link.siteName.isNotEmpty - ? link.siteName + (link.siteName?.isNotEmpty ?? false) + ? link.siteName! : Uri.parse(link.url).host, style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, diff --git a/lib/widgets/content/markdown.dart b/lib/widgets/content/markdown.dart index c3e0436..87721a0 100644 --- a/lib/widgets/content/markdown.dart +++ b/lib/widgets/content/markdown.dart @@ -183,9 +183,15 @@ class MarkdownTextContent extends HookConsumerWidget { ); } } - final content = ConstrainedBox( - constraints: BoxConstraints(maxHeight: 360), - child: UniversalImage(uri: uri.toString(), fit: BoxFit.contain), + final content = ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: 360), + child: UniversalImage( + uri: uri.toString(), + fit: BoxFit.contain, + ), + ), ); return content; }, diff --git a/lib/widgets/share/share_sheet.dart b/lib/widgets/share/share_sheet.dart index 693b6c1..82b58b3 100644 --- a/lib/widgets/share/share_sheet.dart +++ b/lib/widgets/share/share_sheet.dart @@ -879,7 +879,8 @@ class _LinkPreview extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Favicon and image - if (embed.imageUrl != null || embed.faviconUrl.isNotEmpty) + if (embed.imageUrl != null || + (embed.faviconUrl?.isNotEmpty ?? false)) Container( width: 60, height: 60, @@ -899,11 +900,14 @@ class _LinkPreview extends ConsumerWidget { errorBuilder: (context, error, stackTrace) { return _buildFaviconFallback( context, - embed.faviconUrl, + embed.faviconUrl ?? '', ); }, ) - : _buildFaviconFallback(context, embed.faviconUrl), + : _buildFaviconFallback( + context, + embed.faviconUrl ?? '', + ), ), ), // Content @@ -912,9 +916,9 @@ class _LinkPreview extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Site name - if (embed.siteName.isNotEmpty) + if (embed.siteName?.isNotEmpty ?? false) Text( - embed.siteName, + embed.siteName!, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: Theme.of(context).colorScheme.primary, ),