From 2f9af5543eccd8f319f2c969d3274a0523d428fa Mon Sep 17 00:00:00 2001 From: Roscoe Rubin-Rottenberg Date: Thu, 9 Apr 2026 14:05:44 -0400 Subject: [PATCH] Revert "fix: android image rendering" This reverts commit 05a975b2ac9a5c3d19afab87cb4fd9f785b2bd53. --- android/app/build.gradle.kts | 2 + .../components/molecules/feed_card.dart | 11 +-- .../components/molecules/post_tile.dart | 13 +-- .../components/molecules/profile_avatar.dart | 6 +- .../molecules/settings_feed_card.dart | 6 +- .../components/molecules/story_circle.dart | 6 +- .../atproto/data/models/feed_models.dart | 86 +++++++++++-------- .../widgets/story_mention_picker_sheet.dart | 8 +- .../storage/cache/download_manager_impl.dart | 3 +- lib/src/core/ui/widgets/image_content.dart | 49 ++++------- lib/src/core/ui/widgets/user_avatar.dart | 6 +- lib/src/core/utils/image_url_resolver.dart | 49 ----------- .../comments/ui/widgets/comment_item.dart | 3 +- .../feed/ui/widgets/post/post_overlay.dart | 4 +- lib/src/features/home/ui/pages/main_page.dart | 6 +- .../messages/ui/pages/messages_page.dart | 5 +- .../ui/widgets/notification_item.dart | 23 +++-- .../ui/widgets/mention_input_field.dart | 8 +- .../features/search/ui/pages/search_page.dart | 8 +- .../ui/pages/labeler_label_settings_page.dart | 3 +- .../ui/pages/labeler_management_page.dart | 3 +- .../stories/ui/pages/author_stories_page.dart | 20 ++--- .../stories/ui/pages/story_manager_page.dart | 11 +-- .../features/stories/ui/pages/story_page.dart | 8 +- 24 files changed, 129 insertions(+), 218 deletions(-) delete mode 100644 lib/src/core/utils/image_url_resolver.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 211c570..9a03a51 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -68,6 +68,8 @@ android { buildTypes { release { signingConfig = signingConfigs.getByName("release") + isShrinkResources = false + isMinifyEnabled = false } } } diff --git a/lib/src/core/design_system/components/molecules/feed_card.dart b/lib/src/core/design_system/components/molecules/feed_card.dart index e64eb10..4d168b0 100644 --- a/lib/src/core/design_system/components/molecules/feed_card.dart +++ b/lib/src/core/design_system/components/molecules/feed_card.dart @@ -8,7 +8,6 @@ import 'package:spark/src/core/design_system/tokens/shapes.dart'; import 'package:spark/src/core/design_system/tokens/typography.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; class FeedCard extends StatelessWidget { const FeedCard({ @@ -56,7 +55,10 @@ class FeedCard extends StatelessWidget { String? get _description => generator?.description; String get _avatarUrl { - return resolveImageUrlObject(generator?.avatar) ?? ''; + if (generator?.avatar != null) { + return generator!.avatar.toString(); + } + return ''; } bool get _isPrimaryAction => !isAdded || !isPinned; @@ -246,9 +248,8 @@ class _FeedAvatar extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final resolvedImageUrl = resolveImageUrlString(imageUrl); - if (resolvedImageUrl == null) { + if (imageUrl.isEmpty) { return _FallbackAvatar(isDark: isDark); } @@ -256,7 +257,7 @@ class _FeedAvatar extends StatelessWidget { borderRadius: BorderRadius.circular(8), child: CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolvedImageUrl, + imageUrl: imageUrl, width: 36, height: 36, fit: BoxFit.cover, diff --git a/lib/src/core/design_system/components/molecules/post_tile.dart b/lib/src/core/design_system/components/molecules/post_tile.dart index 93f95d7..c30066b 100644 --- a/lib/src/core/design_system/components/molecules/post_tile.dart +++ b/lib/src/core/design_system/components/molecules/post_tile.dart @@ -7,7 +7,6 @@ import 'package:spark/src/core/design_system/components/atoms/icons.dart'; import 'package:spark/src/core/design_system/tokens/colors.dart'; import 'package:spark/src/core/design_system/tokens/shapes.dart'; import 'package:spark/src/core/design_system/tokens/typography.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; class PostTile extends StatelessWidget { final String thumbnailUrl; @@ -32,7 +31,6 @@ class PostTile extends StatelessWidget { @override Widget build(BuildContext context) { - final resolvedThumbnailUrl = resolveImageUrlString(thumbnailUrl); // Squircle shape from design tokens final BorderRadiusGeometry radius = BorderRadius.circular( AppShapes.squircleRadius, @@ -57,17 +55,12 @@ class PostTile extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - if (resolvedThumbnailUrl == null) - const ColoredBox( - color: AppColors.grey800, - child: Icon(Icons.broken_image, color: AppColors.grey400), - ) - else if (nsfwBlur) + if (nsfwBlur) ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolvedThumbnailUrl, + imageUrl: thumbnailUrl, fit: BoxFit.cover, placeholder: (context, url) => const ColoredBox(color: AppColors.grey800), @@ -80,7 +73,7 @@ class PostTile extends StatelessWidget { else CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolvedThumbnailUrl, + imageUrl: thumbnailUrl, fit: BoxFit.cover, placeholder: (context, url) => const ColoredBox(color: AppColors.grey800), diff --git a/lib/src/core/design_system/components/molecules/profile_avatar.dart b/lib/src/core/design_system/components/molecules/profile_avatar.dart index ac9bd13..1983b45 100644 --- a/lib/src/core/design_system/components/molecules/profile_avatar.dart +++ b/lib/src/core/design_system/components/molecules/profile_avatar.dart @@ -3,7 +3,6 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:spark/src/core/design_system/tokens/gradients.dart'; import 'package:spark/src/core/ui/foundation/colors.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; class ProfileAvatar extends StatelessWidget { const ProfileAvatar({ @@ -112,12 +111,11 @@ class ProfileAvatar extends StatelessWidget { required bool isDarkMode, required double avatarSize, }) { - final resolvedAvatarUrl = resolveImageUrlString(avatarUrl); - if (resolvedAvatarUrl != null) { + if (avatarUrl != null && avatarUrl!.isNotEmpty) { return ClipOval( child: CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolvedAvatarUrl, + imageUrl: avatarUrl!, width: avatarSize, height: avatarSize, fit: BoxFit.cover, diff --git a/lib/src/core/design_system/components/molecules/settings_feed_card.dart b/lib/src/core/design_system/components/molecules/settings_feed_card.dart index 671461e..4f1a5c2 100644 --- a/lib/src/core/design_system/components/molecules/settings_feed_card.dart +++ b/lib/src/core/design_system/components/molecules/settings_feed_card.dart @@ -7,7 +7,6 @@ import 'package:spark/src/core/design_system/tokens/shapes.dart'; import 'package:spark/src/core/design_system/tokens/typography.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; enum SettingsFeedCardMode { display, edit } @@ -61,7 +60,10 @@ class SettingsFeedCard extends StatelessWidget { } String get _avatarUrl { - return resolveImageUrlObject(generator?.avatar) ?? ''; + if (generator?.avatar != null) { + return generator!.avatar.toString(); + } + return ''; } @override diff --git a/lib/src/core/design_system/components/molecules/story_circle.dart b/lib/src/core/design_system/components/molecules/story_circle.dart index 5a0e17b..090463c 100644 --- a/lib/src/core/design_system/components/molecules/story_circle.dart +++ b/lib/src/core/design_system/components/molecules/story_circle.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:spark/src/core/design_system/tokens/gradients.dart'; import 'package:spark/src/core/design_system/tokens/typography.dart'; import 'package:spark/src/core/ui/foundation/colors.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; enum StoryType { story, live, cf, create } @@ -85,7 +84,6 @@ class StoryCircle extends StatelessWidget { Widget build(BuildContext context) { final hasStoryRing = type != StoryType.create; final ringColor = _getRingColor(); - final resolvedImageUrl = resolveImageUrlString(imageUrl); return SizedBox( width: _widgetWidth, @@ -113,10 +111,10 @@ class StoryCircle extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(_ringGap), child: ClipOval( - child: resolvedImageUrl != null + child: imageUrl.isNotEmpty ? CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolvedImageUrl, + imageUrl: imageUrl, width: _imageSize, height: _imageSize, fit: BoxFit.cover, diff --git a/lib/src/core/network/atproto/data/models/feed_models.dart b/lib/src/core/network/atproto/data/models/feed_models.dart index 30078c8..3777475 100644 --- a/lib/src/core/network/atproto/data/models/feed_models.dart +++ b/lib/src/core/network/atproto/data/models/feed_models.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:spark/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart'; import 'package:spark/src/core/network/atproto/data/models/models.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/uri_converter.dart'; part 'feed_models.freezed.dart'; @@ -387,7 +386,38 @@ abstract class PostView with _$PostView { /// Resolves AT Protocol blob URLs to HTTP URLs for display String _resolveAtUriToHttpUrl(Uri uri, {bool isFullsize = false}) { - return resolveImageUrlOrEmpty(uri, isFullsize: isFullsize); + final uriString = uri.toString(); + + // If it's already an HTTP URL, return as is + if (uriString.startsWith('http://') || uriString.startsWith('https://')) { + return uriString; + } + + // If it's an AT Protocol blob URL, convert to Bluesky CDN URL + if (uriString.startsWith('at://')) { + // Parse AT URI format: at://did/collection/rkey + final match = RegExp( + r'^at://([^/]+)/([^/]+)/(.+)$', + ).firstMatch(uriString); + if (match != null) { + final did = match.group(1)!; + final collection = match.group(2)!; + final rkey = match.group(3)!; + + // For blob collections, use Bluesky's CDN + if (collection == 'blob') { + if (isFullsize) { + return 'https://cdn.bsky.app/img/feed_fullsize/plain/$did/$rkey@jpeg'; + } else { + return 'https://cdn.bsky.app/img/feed_thumbnail/plain/$did/$rkey@jpeg'; + } + } + } + } + + // Return empty string for unrecognized URI schemes (e.g., file://) + // to prevent invalid URLs from being passed to image loaders + return ''; } String get videoUrl { @@ -416,14 +446,12 @@ abstract class PostView with _$PostView { List get imageUrls { final mediaToCheck = displayMedia; - final List urls; + final List urls; switch (mediaToCheck) { case MediaViewImage(:final image): - urls = [resolveImageUrlObject(image.fullsize, isFullsize: true)]; + urls = [image.fullsize.toString()]; case MediaViewImages(:final images): - urls = images - .map((img) => resolveImageUrlObject(img.fullsize, isFullsize: true)) - .toList(); + urls = images.map((img) => img.fullsize.toString()).toList(); case MediaViewBskyImages(:final images): urls = images .map( @@ -434,14 +462,9 @@ abstract class PostView with _$PostView { // Handle nested media in record with media switch (media) { case MediaViewImage(:final image): - urls = [resolveImageUrlObject(image.fullsize, isFullsize: true)]; + urls = [image.fullsize.toString()]; case MediaViewImages(:final images): - urls = images - .map( - (img) => - resolveImageUrlObject(img.fullsize, isFullsize: true), - ) - .toList(); + urls = images.map((img) => img.fullsize.toString()).toList(); case MediaViewBskyImages(:final images): urls = images .map( @@ -455,33 +478,36 @@ abstract class PostView with _$PostView { case _: urls = []; } - return urls.whereType().toList(); + // Filter out invalid URLs (must be http/https) + return urls + .where((url) => url.startsWith('http://') || url.startsWith('https://')) + .toList(); } String get thumbnailUrl { final mediaToCheck = displayMedia; switch (mediaToCheck) { case MediaViewVideo(:final thumbnail): - return resolveImageUrlOrEmpty(thumbnail); + return thumbnail.toString(); case MediaViewBskyVideo(:final thumbnail): return _resolveAtUriToHttpUrl(thumbnail); case MediaViewImage(:final image): - return resolveImageUrlOrEmpty(image.thumb); + return image.thumb.toString(); case MediaViewImages(:final images): - return resolveImageUrlOrEmpty(images.first.thumb); + return images.first.thumb.toString(); case MediaViewBskyImages(:final images): return _resolveAtUriToHttpUrl(images.first.thumb); case MediaViewBskyRecordWithMedia(:final media): // Handle nested media in record with media switch (media) { case MediaViewVideo(:final thumbnail): - return resolveImageUrlOrEmpty(thumbnail); + return thumbnail.toString(); case MediaViewBskyVideo(:final thumbnail): return _resolveAtUriToHttpUrl(thumbnail); case MediaViewImage(:final image): - return resolveImageUrlOrEmpty(image.thumb); + return image.thumb.toString(); case MediaViewImages(:final images): - return resolveImageUrlOrEmpty(images.first.thumb); + return images.first.thumb.toString(); case MediaViewBskyImages(:final images): return _resolveAtUriToHttpUrl(images.first.thumb); case _: @@ -822,23 +848,11 @@ sealed class ThreadPost with _$ThreadPost { ThreadPostView(:final post) => post.imageUrls, ThreadReplyView(:final reply) => switch (reply.hydratedMedia) { // Replies/comments only support a single image (EmbedViewMediaImage) - MediaViewImage(:final image) => [ - resolveImageUrlObject(image.fullsize, isFullsize: true), - ].whereType().toList(), + MediaViewImage(:final image) => [image.fullsize.toString()], MediaViewImages(:final images) => - images - .map( - (img) => resolveImageUrlOrEmpty(img.fullsize, isFullsize: true), - ) - .where((url) => url.isNotEmpty) - .toList(), + images.map((img) => img.fullsize.toString()).toList(), MediaViewBskyImages(:final images) => - images - .map( - (img) => resolveImageUrlOrEmpty(img.fullsize, isFullsize: true), - ) - .where((url) => url.isNotEmpty) - .toList(), + images.map((img) => img.fullsize.toString()).toList(), _ => [], }, }; diff --git a/lib/src/core/pro_image_editor/ui/widgets/story_mention_picker_sheet.dart b/lib/src/core/pro_image_editor/ui/widgets/story_mention_picker_sheet.dart index ed90e03..c113e80 100644 --- a/lib/src/core/pro_image_editor/ui/widgets/story_mention_picker_sheet.dart +++ b/lib/src/core/pro_image_editor/ui/widgets/story_mention_picker_sheet.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/network/atproto/data/models/actor_models.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/search/providers/actor_typeahead_provider.dart'; import 'package:spark/src/features/search/providers/actor_typeahead_state.dart'; @@ -164,13 +163,14 @@ class _ResultsList extends StatelessWidget { separatorBuilder: (_, _) => const Divider(color: Color(0x1FFFFFFF)), itemBuilder: (context, index) { final actor = typeaheadState.results[index]; - final avatarUrl = resolveImageUrlObject(actor.avatar); return ListTile( contentPadding: EdgeInsets.zero, leading: CircleAvatar( backgroundColor: const Color(0x1AFFFFFF), - backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null, - child: avatarUrl == null + backgroundImage: actor.avatar != null + ? NetworkImage(actor.avatar.toString()) + : null, + child: actor.avatar == null ? const Icon(Icons.person_outline, color: Colors.white) : null, ), diff --git a/lib/src/core/storage/cache/download_manager_impl.dart b/lib/src/core/storage/cache/download_manager_impl.dart index 7cc36bf..619d0bb 100644 --- a/lib/src/core/storage/cache/download_manager_impl.dart +++ b/lib/src/core/storage/cache/download_manager_impl.dart @@ -5,7 +5,6 @@ import 'package:get_it/get_it.dart'; import 'package:pool/pool.dart'; import 'package:spark/src/core/network/atproto/data/models/models.dart'; import 'package:spark/src/core/storage/cache/download_manager_interface.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/logging/logging.dart'; import 'package:spark/src/features/feed/providers/feed_state.dart'; @@ -222,7 +221,7 @@ class DownloadManagerImpl implements DownloadManagerInterface { task.post.videoUrl, placeholder: CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolveImageUrlOrEmpty(thumbnail), + imageUrl: thumbnail.toString(), ), cacheConfiguration: BetterPlayerCacheConfiguration( useCache: true, diff --git a/lib/src/core/ui/widgets/image_content.dart b/lib/src/core/ui/widgets/image_content.dart index f83a7ac..0d29274 100644 --- a/lib/src/core/ui/widgets/image_content.dart +++ b/lib/src/core/ui/widgets/image_content.dart @@ -3,7 +3,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:spark/src/core/ui/foundation/colors.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/feed/ui/widgets/images/image_carousel.dart'; class ImageContent extends StatelessWidget { @@ -52,10 +51,6 @@ class ImageContent extends StatelessWidget { @override Widget build(BuildContext context) { - final resolvedImageUrl = imageUrls.isEmpty - ? null - : resolveImageUrlString(imageUrls.first, isFullsize: true); - return GestureDetector( onTap: () => _showImageCarousel(context), child: ClipRRect( @@ -67,37 +62,24 @@ class ImageContent extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - if (resolvedImageUrl != null) - CachedNetworkImage( - fadeInDuration: Duration.zero, - imageUrl: resolvedImageUrl, - fit: BoxFit.cover, - placeholder: (context, url) => Container( - color: Colors.grey[850]?.withValues(alpha: 128), - child: const Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white54, - ), - ), - ), - ), - errorWidget: (context, url, error) => ColoredBox( - color: AppColors.darkPurple.withValues(alpha: 26), - child: const Center( - child: Icon( - FluentIcons.image_off_24_regular, - size: 24, - color: Colors.white70, + CachedNetworkImage( + fadeInDuration: Duration.zero, + imageUrl: imageUrls.first, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + color: Colors.grey[850]?.withValues(alpha: 128), + child: const Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white54, ), ), ), - ) - else - ColoredBox( + ), + errorWidget: (context, url, error) => ColoredBox( color: AppColors.darkPurple.withValues(alpha: 26), child: const Center( child: Icon( @@ -107,6 +89,7 @@ class ImageContent extends StatelessWidget { ), ), ), + ), if (imageUrls.length > 1) Positioned( diff --git a/lib/src/core/ui/widgets/user_avatar.dart b/lib/src/core/ui/widgets/user_avatar.dart index 43d082b..d6dfdf8 100644 --- a/lib/src/core/ui/widgets/user_avatar.dart +++ b/lib/src/core/ui/widgets/user_avatar.dart @@ -1,7 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; /// A customizable user avatar with fallback options when no image is available class UserAvatar extends StatelessWidget { @@ -27,7 +26,6 @@ class UserAvatar extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; - final resolvedImageUrl = resolveImageUrlString(imageUrl); final effectiveBorderColor = borderColor ?? colorScheme.outline; final effectiveBackgroundColor = backgroundColor ?? colorScheme.primary; @@ -35,7 +33,7 @@ class UserAvatar extends StatelessWidget { fallbackTextColor ?? colorScheme.onPrimary; // If no image URL is provided, show fallback avatar - if (resolvedImageUrl == null) { + if (imageUrl.isEmpty) { return Container( width: size, height: size, @@ -77,7 +75,7 @@ class UserAvatar extends StatelessWidget { clipBehavior: Clip.antiAlias, child: CachedNetworkImage( fadeInDuration: Duration.zero, - imageUrl: resolvedImageUrl, + imageUrl: imageUrl, fit: BoxFit.cover, placeholder: (context, url) => ColoredBox( color: effectiveBackgroundColor, diff --git a/lib/src/core/utils/image_url_resolver.dart b/lib/src/core/utils/image_url_resolver.dart deleted file mode 100644 index 65280eb..0000000 --- a/lib/src/core/utils/image_url_resolver.dart +++ /dev/null @@ -1,49 +0,0 @@ -final RegExp _atUriPattern = RegExp(r'^at://([^/]+)/([^/]+)/(.+)$'); - -String? resolveImageUrlString(String? raw, {bool isFullsize = false}) { - final candidate = raw?.trim(); - if (candidate == null || candidate.isEmpty || candidate == 'null') { - return null; - } - - if (candidate.startsWith('//')) { - return 'https:$candidate'; - } - - final parsed = Uri.tryParse(candidate); - final scheme = parsed?.scheme.toLowerCase(); - if (scheme == 'http' || scheme == 'https') { - return parsed.toString(); - } - - final match = _atUriPattern.firstMatch(candidate); - if (match == null) { - return null; - } - - final did = match.group(1)!; - final collection = match.group(2)!; - final rkey = match.group(3)!; - - if (collection != 'blob') { - return null; - } - - final variant = isFullsize ? 'feed_fullsize' : 'feed_thumbnail'; - return 'https://cdn.bsky.app/img/$variant/plain/$did/$rkey@jpeg'; -} - -String? resolveImageUrlObject(Object? raw, {bool isFullsize = false}) { - final candidate = switch (raw) { - null => null, - String value => value, - Uri value => value.toString(), - _ => raw.toString(), - }; - - return resolveImageUrlString(candidate, isFullsize: isFullsize); -} - -String resolveImageUrlOrEmpty(Object? raw, {bool isFullsize = false}) { - return resolveImageUrlObject(raw, isFullsize: isFullsize) ?? ''; -} diff --git a/lib/src/features/comments/ui/widgets/comment_item.dart b/lib/src/features/comments/ui/widgets/comment_item.dart index a30caba..d9c6de4 100644 --- a/lib/src/features/comments/ui/widgets/comment_item.dart +++ b/lib/src/features/comments/ui/widgets/comment_item.dart @@ -15,7 +15,6 @@ import 'package:spark/src/core/ui/widgets/image_content.dart'; import 'package:spark/src/core/ui/widgets/options_panel.dart'; import 'package:spark/src/core/ui/widgets/report_dialog.dart'; import 'package:spark/src/core/ui/widgets/user_avatar.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/utils.dart'; import 'package:spark/src/features/comments/providers/comment_provider.dart'; import 'package:spark/src/features/comments/providers/comment_state.dart'; @@ -385,7 +384,7 @@ class _Avatar extends StatelessWidget { @override Widget build(BuildContext context) { return UserAvatar( - imageUrl: resolveImageUrlObject(widget.thread.post.author.avatar) ?? '', + imageUrl: widget.thread.post.author.avatar.toString(), username: widget.thread.post.author.handle, size: 36, ); diff --git a/lib/src/features/feed/ui/widgets/post/post_overlay.dart b/lib/src/features/feed/ui/widgets/post/post_overlay.dart index 2359389..6855a71 100644 --- a/lib/src/features/feed/ui/widgets/post/post_overlay.dart +++ b/lib/src/features/feed/ui/widgets/post/post_overlay.dart @@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spark/src/core/design_system/components/molecules/known_interactions_bar.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; import 'package:spark/src/core/utils/label_utils.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/feed/ui/widgets/action_buttons/side_action_bar.dart'; import 'package:spark/src/features/feed/ui/widgets/post/info_bar.dart'; import 'package:spark/src/features/settings/providers/preferences_provider.dart'; @@ -125,8 +124,7 @@ class PostOverlay extends ConsumerWidget { commentCount: '${post.replyCount ?? 0}', shareCount: '${post.repostCount ?? 0}', isLiked: isLiked, - profileImageUrl: - resolveImageUrlObject(post.author.avatar) ?? '', + profileImageUrl: post.author.avatar.toString(), isImage: post.media is MediaViewImages || post.media is MediaViewBskyImages, diff --git a/lib/src/features/home/ui/pages/main_page.dart b/lib/src/features/home/ui/pages/main_page.dart index 77eebe1..3af4c6b 100644 --- a/lib/src/features/home/ui/pages/main_page.dart +++ b/lib/src/features/home/ui/pages/main_page.dart @@ -8,7 +8,6 @@ import 'package:spark/src/core/design_system/components/organisms/bottom_nav_bar import 'package:spark/src/core/notifications/push_notification_service.dart'; import 'package:spark/src/core/routing/app_router.dart'; import 'package:spark/src/core/ui/theme/data/models/app_theme.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/auth/providers/auth_providers.dart'; import 'package:spark/src/features/feed/providers/feed_refresh_trigger_provider.dart'; import 'package:spark/src/features/home/providers/navigation_provider.dart'; @@ -97,9 +96,8 @@ class _MainPageState extends ConsumerState { final profileAsync = userDid != null ? ref.watch(profileProvider(did: userDid)) : null; - final userAvatar = resolveImageUrlObject( - profileAsync?.asData?.value.profile?.avatar, - ); + final userAvatar = profileAsync?.asData?.value.profile?.avatar + ?.toString(); final avatarProvider = userAvatar != null && userAvatar.isNotEmpty ? CachedNetworkImageProvider(userAvatar) diff --git a/lib/src/features/messages/ui/pages/messages_page.dart b/lib/src/features/messages/ui/pages/messages_page.dart index c2acbbd..3901fff 100644 --- a/lib/src/features/messages/ui/pages/messages_page.dart +++ b/lib/src/features/messages/ui/pages/messages_page.dart @@ -4,10 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get_it/get_it.dart'; import 'package:spark/src/core/design_system/templates/chat_list_page_template.dart'; -import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/routing/app_router.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/logging/logging.dart'; +import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/features/messages/providers/conversations_provider.dart'; @RoutePage() @@ -63,7 +62,7 @@ class _MessagesPageState extends ConsumerState { otherUserDid: profile.did, otherUserHandle: profile.handle, otherUserDisplayName: profile.displayName, - otherUserAvatar: resolveImageUrlObject(profile.avatar), + otherUserAvatar: profile.avatar.toString(), ), ); }, diff --git a/lib/src/features/notifications/ui/widgets/notification_item.dart b/lib/src/features/notifications/ui/widgets/notification_item.dart index c635569..0878e95 100644 --- a/lib/src/features/notifications/ui/widgets/notification_item.dart +++ b/lib/src/features/notifications/ui/widgets/notification_item.dart @@ -12,7 +12,6 @@ import 'package:spark/src/core/network/atproto/data/repositories/sprk_repository import 'package:spark/src/core/routing/app_router.dart'; import 'package:spark/src/core/ui/foundation/colors.dart'; import 'package:spark/src/core/ui/widgets/user_avatar.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/messages/ui/pages/chat_page.dart'; import 'package:spark/src/features/notifications/models/grouped_notification.dart'; @@ -333,11 +332,11 @@ class _NotificationItemState extends ConsumerState { case 'so.sprk.media.image#view': final thumb = media['thumb']; if (thumb != null) { - return resolveImageUrlObject(thumb); + return thumb is String ? thumb : thumb.toString(); } final fullsize = media['fullsize']; if (fullsize != null) { - return resolveImageUrlObject(fullsize, isFullsize: true); + return fullsize is String ? fullsize : fullsize.toString(); } // Multiple images - get first one @@ -348,11 +347,11 @@ class _NotificationItemState extends ConsumerState { if (firstImage != null) { final thumb = firstImage['thumb']; if (thumb != null) { - return resolveImageUrlObject(thumb); + return thumb is String ? thumb : thumb.toString(); } final fullsize = firstImage['fullsize']; if (fullsize != null) { - return resolveImageUrlObject(fullsize, isFullsize: true); + return fullsize is String ? fullsize : fullsize.toString(); } } } @@ -362,7 +361,7 @@ class _NotificationItemState extends ConsumerState { case 'app.bsky.embed.video#view': final thumbnail = media['thumbnail']; if (thumbnail != null) { - return resolveImageUrlObject(thumbnail); + return thumbnail is String ? thumbnail : thumbnail.toString(); } // Bluesky images @@ -373,11 +372,11 @@ class _NotificationItemState extends ConsumerState { if (firstImage != null) { final thumb = firstImage['thumb']; if (thumb != null) { - return resolveImageUrlObject(thumb); + return thumb is String ? thumb : thumb.toString(); } final fullsize = firstImage['fullsize']; if (fullsize != null) { - return resolveImageUrlObject(fullsize, isFullsize: true); + return fullsize is String ? fullsize : fullsize.toString(); } } } @@ -394,14 +393,14 @@ class _NotificationItemState extends ConsumerState { if (firstImage != null) { final thumb = firstImage['thumb']; if (thumb != null) { - return resolveImageUrlObject(thumb); + return thumb is String ? thumb : thumb.toString(); } } } } else if (nestedType == 'app.bsky.embed.video#view') { final thumbnail = nestedMedia['thumbnail']; if (thumbnail != null) { - return resolveImageUrlObject(thumbnail); + return thumbnail is String ? thumbnail : thumbnail.toString(); } } } @@ -438,7 +437,7 @@ class _NotificationItemState extends ConsumerState { if (authors.length == 1) { // Single avatar final author = authors[0].author; - final avatarUrl = resolveImageUrlObject(author.avatar) ?? ''; + final avatarUrl = author.avatar?.toString() ?? ''; final username = author.displayName ?? author.handle; final handleHash = author.handle.hashCode; @@ -468,7 +467,7 @@ class _NotificationItemState extends ConsumerState { ...authors.asMap().entries.map((entry) { final index = entry.key; final author = entry.value.author; - final avatarUrl = resolveImageUrlObject(author.avatar) ?? ''; + final avatarUrl = author.avatar?.toString() ?? ''; final username = author.displayName ?? author.handle; final handleHash = author.handle.hashCode; diff --git a/lib/src/features/posting/ui/widgets/mention_input_field.dart b/lib/src/features/posting/ui/widgets/mention_input_field.dart index e36e70c..8d51119 100644 --- a/lib/src/features/posting/ui/widgets/mention_input_field.dart +++ b/lib/src/features/posting/ui/widgets/mention_input_field.dart @@ -3,7 +3,6 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spark/src/core/design_system/components/molecules/input_field.dart'; import 'package:spark/src/core/design_system/tokens/constants.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/text_formatter.dart'; import 'package:spark/src/features/posting/models/mention.dart'; import 'package:spark/src/features/posting/models/mention_controller.dart'; @@ -386,15 +385,14 @@ class _MentionInputFieldState extends ConsumerState { itemCount: typeaheadState.results.length, itemBuilder: (context, index) { final actor = typeaheadState.results[index]; - final avatarUrl = resolveImageUrlObject(actor.avatar); return ListTile( dense: true, leading: CircleAvatar( radius: 16, - backgroundImage: avatarUrl != null - ? NetworkImage(avatarUrl) + backgroundImage: actor.avatar != null + ? NetworkImage(actor.avatar.toString()) : null, - child: avatarUrl == null + child: actor.avatar == null ? const Icon(Icons.person, size: 16) : null, ), diff --git a/lib/src/features/search/ui/pages/search_page.dart b/lib/src/features/search/ui/pages/search_page.dart index 999c901..1c0c931 100644 --- a/lib/src/features/search/ui/pages/search_page.dart +++ b/lib/src/features/search/ui/pages/search_page.dart @@ -9,7 +9,6 @@ import 'package:spark/src/core/design_system/templates/explore_page_template.dar import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/network/atproto/data/models/actor_models.dart'; import 'package:spark/src/core/routing/app_router.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/search/providers/actor_typeahead_provider.dart'; import 'package:spark/src/features/search/providers/actor_typeahead_state.dart'; import 'package:spark/src/features/search/providers/post_search_provider.dart'; @@ -223,15 +222,16 @@ class _ActorTypeaheadSuggestions extends StatelessWidget { separatorBuilder: (context, index) => const Divider(height: 1), itemBuilder: (context, index) { final actor = state.results[index]; - final avatarUrl = resolveImageUrlObject(actor.avatar); return ListTile( onTap: () => onSuggestionSelected(actor), contentPadding: const EdgeInsets.symmetric(vertical: 4), leading: CircleAvatar( radius: 18, - backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null, - child: avatarUrl == null ? const Icon(Icons.person) : null, + backgroundImage: actor.avatar != null + ? NetworkImage(actor.avatar.toString()) + : null, + child: actor.avatar == null ? const Icon(Icons.person) : null, ), title: Text(actor.displayName ?? actor.handle), subtitle: Text('@${actor.handle}'), diff --git a/lib/src/features/settings/ui/pages/labeler_label_settings_page.dart b/lib/src/features/settings/ui/pages/labeler_label_settings_page.dart index 40aa846..8c3478b 100644 --- a/lib/src/features/settings/ui/pages/labeler_label_settings_page.dart +++ b/lib/src/features/settings/ui/pages/labeler_label_settings_page.dart @@ -12,7 +12,6 @@ import 'package:spark/src/core/network/atproto/data/models/labeler_models.dart'; import 'package:spark/src/core/network/atproto/data/repositories/actor_repository.dart'; import 'package:spark/src/core/network/atproto/data/repositories/sprk_repository.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/logging/logging.dart'; import 'package:spark/src/features/settings/providers/settings_provider.dart'; import 'package:spark/src/features/settings/ui/widgets/widgets.dart'; @@ -571,7 +570,7 @@ class _LabelerLabelSettingsPageState borderRadius: BorderRadius.circular(100), child: profile.avatar != null ? CachedNetworkImage( - imageUrl: resolveImageUrlOrEmpty(profile.avatar), + imageUrl: profile.avatar!.toString(), width: 36, height: 36, fit: BoxFit.cover, diff --git a/lib/src/features/settings/ui/pages/labeler_management_page.dart b/lib/src/features/settings/ui/pages/labeler_management_page.dart index 3cb0711..cb65655 100644 --- a/lib/src/features/settings/ui/pages/labeler_management_page.dart +++ b/lib/src/features/settings/ui/pages/labeler_management_page.dart @@ -10,7 +10,6 @@ import 'package:spark/src/core/network/atproto/data/models/actor_models.dart'; import 'package:spark/src/core/network/atproto/data/repositories/actor_repository.dart'; import 'package:spark/src/core/network/atproto/data/repositories/sprk_repository.dart'; import 'package:spark/src/core/routing/app_router.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/core/utils/logging/logging.dart'; import 'package:spark/src/features/settings/providers/settings_provider.dart'; @@ -399,7 +398,7 @@ class _LabelerManagementPageState extends ConsumerState borderRadius: BorderRadius.circular(100), child: profile.avatar != null ? CachedNetworkImage( - imageUrl: resolveImageUrlOrEmpty(profile.avatar), + imageUrl: profile.avatar!.toString(), width: 36, height: 36, fit: BoxFit.cover, diff --git a/lib/src/features/stories/ui/pages/author_stories_page.dart b/lib/src/features/stories/ui/pages/author_stories_page.dart index 802a654..d8196bb 100644 --- a/lib/src/features/stories/ui/pages/author_stories_page.dart +++ b/lib/src/features/stories/ui/pages/author_stories_page.dart @@ -8,7 +8,6 @@ import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/network/atproto/data/models/actor_models.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; import 'package:spark/src/core/routing/app_router.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/stories/ui/pages/story_page.dart'; @RoutePage() @@ -254,8 +253,6 @@ class _AuthorStoriesPageState extends ConsumerState return Scaffold(body: Center(child: Text(l10n.emptyNoStories))); } - final avatarUrl = resolveImageUrlObject(widget.author.avatar); - return Scaffold( backgroundColor: Colors.black, body: SafeArea( @@ -364,20 +361,15 @@ class _AuthorStoriesPageState extends ConsumerState shape: BoxShape.circle, ), child: ClipOval( - child: avatarUrl != null - ? CachedNetworkImage( - imageUrl: avatarUrl, - fit: BoxFit.cover, - errorWidget: (context, url, error) => - const Icon( - Icons.person, - color: Colors.white, - ), - ) - : const Icon( + child: CachedNetworkImage( + imageUrl: widget.author.avatar.toString(), + fit: BoxFit.cover, + errorWidget: (context, url, error) => + const Icon( Icons.person, color: Colors.white, ), + ), ), ), ), diff --git a/lib/src/features/stories/ui/pages/story_manager_page.dart b/lib/src/features/stories/ui/pages/story_manager_page.dart index 6025664..a20607c 100644 --- a/lib/src/features/stories/ui/pages/story_manager_page.dart +++ b/lib/src/features/stories/ui/pages/story_manager_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; import 'package:spark/src/core/routing/app_router.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:spark/src/features/stories/providers/story_auto_delete_provider.dart'; import 'package:spark/src/features/stories/providers/story_manager_provider.dart'; @@ -100,13 +99,9 @@ class StoryManagerPage extends ConsumerWidget { ? '${age.inMinutes}m' : 'now'; final thumbUrl = switch (story.media) { - MediaViewVideo(:final thumbnail) => resolveImageUrlOrEmpty( - thumbnail, - ), - MediaViewImage(:final image) => resolveImageUrlOrEmpty( - image.thumb, - ), - _ => resolveImageUrlOrEmpty(story.author.avatar), + MediaViewVideo(:final thumbnail) => thumbnail.toString(), + MediaViewImage(:final image) => image.thumb.toString(), + _ => story.author.avatar.toString(), }; return Material( color: Colors.transparent, diff --git a/lib/src/features/stories/ui/pages/story_page.dart b/lib/src/features/stories/ui/pages/story_page.dart index 7cb3913..ef62e14 100644 --- a/lib/src/features/stories/ui/pages/story_page.dart +++ b/lib/src/features/stories/ui/pages/story_page.dart @@ -8,7 +8,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; import 'package:spark/src/core/network/atproto/data/models/story_embed_models.dart'; import 'package:spark/src/core/routing/app_router.dart'; -import 'package:spark/src/core/utils/image_url_resolver.dart'; import 'package:video_player/video_player.dart'; @RoutePage() @@ -145,11 +144,8 @@ class _StoryPageState extends ConsumerState String _getImageUrl(StoryView story) { return switch (story.media) { - MediaViewImage(:final image) => resolveImageUrlOrEmpty( - image.fullsize, - isFullsize: true, - ), - _ => resolveImageUrlOrEmpty(widget.story.author.avatar), + MediaViewImage(:final image) => image.fullsize.toString(), + _ => widget.story.author.avatar.toString(), }; } -- 2.51.2