diff --git a/lib/components/Album/AlbumCard.dart b/lib/components/Album/AlbumCard.dart index c439d609..b12b9687 100644 --- a/lib/components/Album/AlbumCard.dart +++ b/lib/components/Album/AlbumCard.dart @@ -21,7 +21,10 @@ class AlbumCard extends HookConsumerWidget { final int marginH = useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20); return PlaybuttonCard( - imageUrl: TypeConversionUtils.image_X_UrlString(album.images), + imageUrl: TypeConversionUtils.image_X_UrlString( + album.images, + placeholder: ImagePlaceholder.collection, + ), margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()), isPlaying: isPlaylistPlaying && playback.isPlaying, isLoading: playback.status == PlaybackStatus.loading && diff --git a/lib/components/Album/AlbumView.dart b/lib/components/Album/AlbumView.dart index 701aaa5f..0aec7a4a 100644 --- a/lib/components/Album/AlbumView.dart +++ b/lib/components/Album/AlbumView.dart @@ -27,7 +27,10 @@ class AlbumView extends HookConsumerWidget { tracks: tracks, id: album.id!, name: album.name!, - thumbnail: TypeConversionUtils.image_X_UrlString(album.images), + thumbnail: TypeConversionUtils.image_X_UrlString( + album.images, + placeholder: ImagePlaceholder.collection, + ), ), tracks.indexWhere((s) => s.id == currentTrack?.id), ); @@ -50,7 +53,10 @@ class AlbumView extends HookConsumerWidget { ref.watch(albumIsSavedForCurrentUserQuery(album.id!)); final albumArt = useMemoized( - () => TypeConversionUtils.image_X_UrlString(album.images), + () => TypeConversionUtils.image_X_UrlString( + album.images, + placeholder: ImagePlaceholder.albumArt, + ), [album.images]); final breakpoint = useBreakpoints(); diff --git a/lib/components/Artist/ArtistCard.dart b/lib/components/Artist/ArtistCard.dart index c6007274..7b43cd79 100644 --- a/lib/components/Artist/ArtistCard.dart +++ b/lib/components/Artist/ArtistCard.dart @@ -4,6 +4,8 @@ import 'package:go_router/go_router.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Shared/HoverBuilder.dart'; import 'package:spotube/components/Shared/SpotubeMarqueeText.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; +import 'package:spotube/utils/type_conversion_utils.dart'; class ArtistCard extends StatelessWidget { final Artist artist; @@ -11,11 +13,11 @@ class ArtistCard extends StatelessWidget { @override Widget build(BuildContext context) { - final backgroundImage = CachedNetworkImageProvider((artist - .images?.isNotEmpty ?? - false) - ? artist.images!.first.url! - : "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6"); + final backgroundImage = + UniversalImage.imageProvider(TypeConversionUtils.image_X_UrlString( + artist.images, + placeholder: ImagePlaceholder.artist, + )); return SizedBox( height: 240, width: 200, diff --git a/lib/components/Artist/ArtistProfile.dart b/lib/components/Artist/ArtistProfile.dart index f5a94da8..7ace926d 100644 --- a/lib/components/Artist/ArtistProfile.dart +++ b/lib/components/Artist/ArtistProfile.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -10,6 +9,7 @@ import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/LoaderShimmers/ShimmerArtistProfile.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TrackTile.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; import 'package:spotube/hooks/useBreakpointValue.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/models/CurrentPlaylist.dart'; @@ -78,8 +78,11 @@ class ArtistProfile extends HookConsumerWidget { const SizedBox(width: 50), CircleAvatar( radius: avatarWidth, - backgroundImage: CachedNetworkImageProvider( - TypeConversionUtils.image_X_UrlString(data.images), + backgroundImage: UniversalImage.imageProvider( + TypeConversionUtils.image_X_UrlString( + data.images, + placeholder: ImagePlaceholder.artist, + ), ), ), Padding( @@ -193,7 +196,9 @@ class ArtistProfile extends HookConsumerWidget { id: data.id!, name: "${data.name!} To Tracks", thumbnail: TypeConversionUtils.image_X_UrlString( - data.images), + data.images, + placeholder: ImagePlaceholder.artist, + ), ), tracks.indexWhere((s) => s.id == currentTrack?.id), ); @@ -233,10 +238,10 @@ class ArtistProfile extends HookConsumerWidget { "${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; String? thumbnailUrl = TypeConversionUtils.image_X_UrlString( - track.value.album?.images, - index: - (track.value.album?.images?.length ?? 1) - - 1); + track.value.album?.images, + index: (track.value.album?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.albumArt, + ); return TrackTile( playback, duration: duration, diff --git a/lib/components/Home/Sidebar.dart b/lib/components/Home/Sidebar.dart index 6b2459c4..83c72969 100644 --- a/lib/components/Home/Sidebar.dart +++ b/lib/components/Home/Sidebar.dart @@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter/material.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; import 'package:spotube/hooks/useBreakpointValue.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/models/sideBarTiles.dart'; @@ -135,8 +136,10 @@ class Sidebar extends HookConsumerWidget { final data = meSnapshot.asData?.value; final avatarImg = TypeConversionUtils.image_X_UrlString( - data?.images, - index: (data?.images?.length ?? 1) - 1); + data?.images, + index: (data?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.artist, + ); if (extended.value) { return Padding( padding: const EdgeInsets.all(16), @@ -155,7 +158,8 @@ class Sidebar extends HookConsumerWidget { children: [ CircleAvatar( backgroundImage: - CachedNetworkImageProvider(avatarImg), + UniversalImage.imageProvider( + avatarImg), onBackgroundImageError: (exception, stackTrace) => Image.asset( @@ -193,7 +197,7 @@ class Sidebar extends HookConsumerWidget { onTap: () => goToSettings(context), child: CircleAvatar( backgroundImage: - CachedNetworkImageProvider(avatarImg), + UniversalImage.imageProvider(avatarImg), onBackgroundImageError: (exception, stackTrace) => Image.asset( "assets/user-placeholder.png", diff --git a/lib/components/Library/UserDownloads.dart b/lib/components/Library/UserDownloads.dart index 916c0c95..c8bed68d 100644 --- a/lib/components/Library/UserDownloads.dart +++ b/lib/components/Library/UserDownloads.dart @@ -3,6 +3,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; import 'package:spotube/provider/Downloader.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -53,11 +54,12 @@ class UserDownloads extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 5), child: ClipRRect( borderRadius: BorderRadius.circular(10), - child: CachedNetworkImage( + child: UniversalImage( height: 40, width: 40, - imageUrl: TypeConversionUtils.image_X_UrlString( + path: TypeConversionUtils.image_X_UrlString( track.album?.images, + placeholder: ImagePlaceholder.albumArt, ), ), ), diff --git a/lib/components/Library/UserLocalTracks.dart b/lib/components/Library/UserLocalTracks.dart index ebbd3c25..947c70a0 100644 --- a/lib/components/Library/UserLocalTracks.dart +++ b/lib/components/Library/UserLocalTracks.dart @@ -108,7 +108,10 @@ class UserLocalTracks extends HookConsumerWidget { tracks: tracks, id: "local", name: "Local Tracks", - thumbnail: TypeConversionUtils.image_X_UrlString(null), + thumbnail: TypeConversionUtils.image_X_UrlString( + null, + placeholder: ImagePlaceholder.collection, + ), isLocal: true, ), tracks.indexWhere((s) => s.id == currentTrack?.id), diff --git a/lib/components/Lyrics/SyncedLyrics.dart b/lib/components/Lyrics/SyncedLyrics.dart index 838c91d6..eebe336f 100644 --- a/lib/components/Lyrics/SyncedLyrics.dart +++ b/lib/components/Lyrics/SyncedLyrics.dart @@ -112,6 +112,7 @@ class SyncedLyrics extends HookConsumerWidget { () => TypeConversionUtils.image_X_UrlString( playback.track?.album?.images, index: (playback.track?.album?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.albumArt, ), [playback.track?.album?.images], ); diff --git a/lib/components/Player/Player.dart b/lib/components/Player/Player.dart index 3a0e0832..1109c6a8 100644 --- a/lib/components/Player/Player.dart +++ b/lib/components/Player/Player.dart @@ -25,6 +25,7 @@ class Player extends HookConsumerWidget { ? TypeConversionUtils.image_X_UrlString( playback.track?.album?.images, index: (playback.track?.album?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.albumArt, ) : "assets/album-placeholder.png", [playback.track?.album?.images], diff --git a/lib/components/Player/PlayerQueue.dart b/lib/components/Player/PlayerQueue.dart index aafdbd59..e371a9ae 100644 --- a/lib/components/Player/PlayerQueue.dart +++ b/lib/components/Player/PlayerQueue.dart @@ -113,6 +113,7 @@ class PlayerQueue extends HookConsumerWidget { duration: duration, thumbnailUrl: TypeConversionUtils.image_X_UrlString( track.value.album?.images, + placeholder: ImagePlaceholder.albumArt, ), isActive: playback.track?.id == track.value.id, onTrackPlayButtonPressed: (currentTrack) async { diff --git a/lib/components/Player/PlayerView.dart b/lib/components/Player/PlayerView.dart index 4a33c16d..d3aece58 100644 --- a/lib/components/Player/PlayerView.dart +++ b/lib/components/Player/PlayerView.dart @@ -42,6 +42,7 @@ class PlayerView extends HookConsumerWidget { () => TypeConversionUtils.image_X_UrlString( currentTrack?.album?.images, index: (currentTrack?.album?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.albumArt, ), [currentTrack?.album?.images], ); diff --git a/lib/components/Playlist/PlaylistCard.dart b/lib/components/Playlist/PlaylistCard.dart index 1fe96c57..7827f51c 100644 --- a/lib/components/Playlist/PlaylistCard.dart +++ b/lib/components/Playlist/PlaylistCard.dart @@ -23,7 +23,10 @@ class PlaylistCard extends HookConsumerWidget { return PlaybuttonCard( margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()), title: playlist.name!, - imageUrl: TypeConversionUtils.image_X_UrlString(playlist.images), + imageUrl: TypeConversionUtils.image_X_UrlString( + playlist.images, + placeholder: ImagePlaceholder.collection, + ), isPlaying: isPlaylistPlaying && playback.isPlaying, isLoading: playback.status == PlaybackStatus.loading && isPlaylistPlaying, onTap: () { @@ -56,7 +59,10 @@ class PlaylistCard extends HookConsumerWidget { tracks: tracks, id: playlist.id!, name: playlist.name!, - thumbnail: TypeConversionUtils.image_X_UrlString(playlist.images), + thumbnail: TypeConversionUtils.image_X_UrlString( + playlist.images, + placeholder: ImagePlaceholder.collection, + ), ), ); }, diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index 00b3bd6f..7f8d5bf0 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -33,7 +33,10 @@ class PlaylistView extends HookConsumerWidget { tracks: tracks, id: playlist.id!, name: playlist.name!, - thumbnail: TypeConversionUtils.image_X_UrlString(playlist.images), + thumbnail: TypeConversionUtils.image_X_UrlString( + playlist.images, + placeholder: ImagePlaceholder.collection, + ), ), tracks.indexWhere((s) => s.id == currentTrack?.id), ); @@ -58,7 +61,10 @@ class PlaylistView extends HookConsumerWidget { final tracksSnapshot = ref.watch(playlistTracksQuery(playlist.id!)); final titleImage = useMemoized( - () => TypeConversionUtils.image_X_UrlString(playlist.images), + () => TypeConversionUtils.image_X_UrlString( + playlist.images, + placeholder: ImagePlaceholder.collection, + ), [playlist.images]); final color = usePaletteGenerator( diff --git a/lib/components/Search/Search.dart b/lib/components/Search/Search.dart index 44188fef..47404d18 100644 --- a/lib/components/Search/Search.dart +++ b/lib/components/Search/Search.dart @@ -110,7 +110,9 @@ class Search extends HookConsumerWidget { duration: duration, thumbnailUrl: TypeConversionUtils.image_X_UrlString( - track.value.album?.images), + track.value.album?.images, + placeholder: ImagePlaceholder.albumArt, + ), isActive: playback.track?.id == track.value.id, onTrackPlayButtonPressed: (currentTrack) async { var isPlaylistPlaying = @@ -126,6 +128,8 @@ class Search extends HookConsumerWidget { thumbnail: TypeConversionUtils .image_X_UrlString( currentTrack.album?.images, + placeholder: + ImagePlaceholder.albumArt, ), ), ); diff --git a/lib/components/Shared/DownloadConfirmationDialog.dart b/lib/components/Shared/DownloadConfirmationDialog.dart index 4d7f61fd..0d2093eb 100644 --- a/lib/components/Shared/DownloadConfirmationDialog.dart +++ b/lib/components/Shared/DownloadConfirmationDialog.dart @@ -1,5 +1,5 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; class DownloadConfirmationDialog extends StatelessWidget { const DownloadConfirmationDialog({Key? key}) : super(key: key); @@ -9,11 +9,11 @@ class DownloadConfirmationDialog extends StatelessWidget { return AlertDialog( contentPadding: const EdgeInsets.all(15), title: Row( - children: [ - const Text("Are you sure?"), - const SizedBox(width: 10), - CachedNetworkImage( - imageUrl: + children: const [ + Text("Are you sure?"), + SizedBox(width: 10), + UniversalImage( + path: "https://c.tenor.com/kHcmsxlKHEAAAAAM/rock-one-eyebrow-raised-rock-staring.gif", height: 40, width: 40, diff --git a/lib/components/Shared/PlaybuttonCard.dart b/lib/components/Shared/PlaybuttonCard.dart index 40ba479c..04c9dba9 100644 --- a/lib/components/Shared/PlaybuttonCard.dart +++ b/lib/components/Shared/PlaybuttonCard.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:spotube/components/Shared/HoverBuilder.dart'; import 'package:spotube/components/Shared/SpotubeMarqueeText.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; class PlaybuttonCard extends StatelessWidget { final void Function()? onTap; @@ -55,8 +56,8 @@ class PlaybuttonCard extends StatelessWidget { children: [ ClipRRect( borderRadius: BorderRadius.circular(8), - child: CachedNetworkImage( - imageUrl: imageUrl, + child: UniversalImage( + path: imageUrl, placeholder: (context, url) => Image.asset("assets/placeholder.png"), ), diff --git a/lib/components/Shared/TrackCollectionView.dart b/lib/components/Shared/TrackCollectionView.dart index 1e3b1a91..edcb3cc0 100644 --- a/lib/components/Shared/TrackCollectionView.dart +++ b/lib/components/Shared/TrackCollectionView.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/components/LoaderShimmers/ShimmerTrackTile.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; +import 'package:spotube/components/Shared/UniversalImage.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/hooks/useCustomStatusBarColor.dart'; import 'package:spotube/hooks/usePaletteColor.dart'; @@ -175,9 +176,7 @@ class TrackCollectionView extends HookConsumerWidget { const BoxConstraints(maxHeight: 200), child: ClipRRect( borderRadius: BorderRadius.circular(10), - child: CachedNetworkImage( - imageUrl: titleImage, - ), + child: UniversalImage(path: titleImage), ), ), Column( diff --git a/lib/components/Shared/TracksTableView.dart b/lib/components/Shared/TracksTableView.dart index 4edcbd88..ff9bbbab 100644 --- a/lib/components/Shared/TracksTableView.dart +++ b/lib/components/Shared/TracksTableView.dart @@ -149,6 +149,7 @@ class TracksTableView extends HookConsumerWidget { String? thumbnailUrl = TypeConversionUtils.image_X_UrlString( track.value.album?.images, index: (track.value.album?.images?.length ?? 1) - 1, + placeholder: ImagePlaceholder.albumArt, ); String duration = "${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; diff --git a/lib/provider/Downloader.dart b/lib/provider/Downloader.dart index d7fc531b..30596cc0 100644 --- a/lib/provider/Downloader.dart +++ b/lib/provider/Downloader.dart @@ -98,12 +98,9 @@ class Downloader with ChangeNotifier { ); final imageUri = TypeConversionUtils.image_X_UrlString( track.album?.images ?? [], + placeholder: ImagePlaceholder.online, ); - final response = await get( - Uri.parse( - imageUri, - ), - ); + final response = await get(Uri.parse(imageUri)); await MetadataGod.writeMetadata( file, diff --git a/lib/provider/Playback.dart b/lib/provider/Playback.dart index 4bbd48a3..b4ec38de 100644 --- a/lib/provider/Playback.dart +++ b/lib/provider/Playback.dart @@ -210,6 +210,7 @@ class Playback extends PersistedChangeNotifier { artUri: Uri.parse( TypeConversionUtils.image_X_UrlString( track.album?.images, + placeholder: ImagePlaceholder.online, ), ), duration: track.ytTrack.duration, diff --git a/lib/provider/SpotifyRequests.dart b/lib/provider/SpotifyRequests.dart index 84a1efb3..b46a1f65 100644 --- a/lib/provider/SpotifyRequests.dart +++ b/lib/provider/SpotifyRequests.dart @@ -133,7 +133,10 @@ final currentUserQuery = FutureProvider( Image() ..height = 50 ..width = 50 - ..url = TypeConversionUtils.image_X_UrlString(me.images), + ..url = TypeConversionUtils.image_X_UrlString( + me.images, + placeholder: ImagePlaceholder.artist, + ), ]; } return me; diff --git a/lib/services/LinuxAudioService.dart b/lib/services/LinuxAudioService.dart index 29d56be9..b5544ad7 100644 --- a/lib/services/LinuxAudioService.dart +++ b/lib/services/LinuxAudioService.dart @@ -298,7 +298,9 @@ class _MprisMediaPlayer2Player extends DBusObject { "mpris:length": DBusInt32(playback.currentDuration.inMicroseconds), "mpris:artUrl": DBusString( TypeConversionUtils.image_X_UrlString( - playback.track?.album?.images), + playback.track?.album?.images, + placeholder: ImagePlaceholder.albumArt, + ), ), "xesam:album": DBusString(playback.track!.album!.name!), "xesam:artist": DBusArray.string( diff --git a/lib/utils/type_conversion_utils.dart b/lib/utils/type_conversion_utils.dart index cfd009f8..b08f0be1 100644 --- a/lib/utils/type_conversion_utils.dart +++ b/lib/utils/type_conversion_utils.dart @@ -11,11 +11,29 @@ import 'package:spotube/models/SpotubeTrack.dart'; import 'package:spotube/utils/primitive_utils.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; +enum ImagePlaceholder { + albumArt, + artist, + collection, + online, +} + abstract class TypeConversionUtils { - static String image_X_UrlString(List? images, {int index = 0}) { + static String image_X_UrlString( + List? images, { + int index = 0, + required ImagePlaceholder placeholder, + }) { + final String placeholderUrl = { + ImagePlaceholder.albumArt: "assets/album-placeholder.png", + ImagePlaceholder.artist: "assets/user-placeholder.png", + ImagePlaceholder.collection: "assets/placeholder.png", + ImagePlaceholder.online: + "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", + }[placeholder]!; return images != null && images.isNotEmpty ? images[0].url! - : "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png"; + : placeholderUrl; } static String artists_X_String(List artists) {