diff --git a/lib/collections/fake.dart b/lib/collections/fake.dart index 8af40e71..840c75f9 100644 --- a/lib/collections/fake.dart +++ b/lib/collections/fake.dart @@ -1,5 +1,6 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/spotify/home_feed.dart'; import 'package:spotube/models/spotify_friends.dart'; import 'package:spotube/provider/history/summary.dart'; @@ -64,24 +65,26 @@ abstract class FakeData { ..uri = "uri" ..externalUrls = externalUrls; - static final AlbumSimple albumSimple = AlbumSimple() - ..id = "1" - ..albumType = AlbumType.album - ..artists = [artistSimple] - ..availableMarkets = [Market.BD] - ..externalUrls = externalUrls - ..href = "text" - ..images = [image] - ..name = "A good album" - ..releaseDate = "2021-01-01" - ..releaseDatePrecision = DatePrecision.day - ..type = "type" - ..uri = "uri"; + static final SpotubeSimpleAlbumObject albumSimple = SpotubeSimpleAlbumObject( + albumType: SpotubeAlbumType.album, + artists: [], + externalUri: "https://example.com", + id: "1", + name: "A good album", + releaseDate: "2021-01-01", + images: [ + SpotubeImageObject( + height: 1, + width: 1, + url: "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg", + ) + ], + ); static final Track track = Track() ..id = "1" ..artists = [artist, artist, artist] - ..album = albumSimple + // ..album = albumSimple ..availableMarkets = [Market.BD] ..discNumber = 1 ..durationMs = 50000 diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index 291254d7..798c0ad4 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -10,9 +10,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_route/auto_route.dart' as _i44; import 'package:flutter/material.dart' as _i45; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i47; -import 'package:spotify/spotify.dart' as _i46; -import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i48; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i48; +import 'package:spotify/spotify.dart' as _i47; +import 'package:spotube/models/metadata/metadata.dart' as _i46; +import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i49; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -86,7 +87,7 @@ class AlbumRoute extends _i44.PageRouteInfo { AlbumRoute({ _i45.Key? key, required String id, - required _i46.AlbumSimple album, + required _i46.SpotubeSimpleAlbumObject album, List<_i44.PageRouteInfo>? children, }) : super( AlbumRoute.name, @@ -125,7 +126,7 @@ class AlbumRouteArgs { final String id; - final _i46.AlbumSimple album; + final _i46.SpotubeSimpleAlbumObject album; @override String toString() { @@ -264,7 +265,7 @@ class GenrePlaylistsRoute extends _i44.PageRouteInfo { GenrePlaylistsRoute({ _i45.Key? key, required String id, - required _i46.Category category, + required _i47.Category category, List<_i44.PageRouteInfo>? children, }) : super( GenrePlaylistsRoute.name, @@ -303,7 +304,7 @@ class GenrePlaylistsRouteArgs { final String id; - final _i46.Category category; + final _i47.Category category; @override String toString() { @@ -335,7 +336,7 @@ class GettingStartedRoute extends _i44.PageRouteInfo { class HomeFeedSectionRoute extends _i44.PageRouteInfo { HomeFeedSectionRoute({ - _i47.Key? key, + _i48.Key? key, required String sectionUri, List<_i44.PageRouteInfo>? children, }) : super( @@ -371,7 +372,7 @@ class HomeFeedSectionRouteArgs { required this.sectionUri, }); - final _i47.Key? key; + final _i48.Key? key; final String sectionUri; @@ -443,7 +444,7 @@ class LibraryRoute extends _i44.PageRouteInfo { class LikedPlaylistRoute extends _i44.PageRouteInfo { LikedPlaylistRoute({ _i45.Key? key, - required _i46.PlaylistSimple playlist, + required _i46.SpotubeSimplePlaylistObject playlist, List<_i44.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, @@ -476,7 +477,7 @@ class LikedPlaylistRouteArgs { final _i45.Key? key; - final _i46.PlaylistSimple playlist; + final _i46.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -584,8 +585,8 @@ class LyricsRoute extends _i44.PageRouteInfo { /// [_i18.MiniLyricsPage] class MiniLyricsRoute extends _i44.PageRouteInfo { MiniLyricsRoute({ - _i47.Key? key, - required _i47.Size prevSize, + _i48.Key? key, + required _i48.Size prevSize, List<_i44.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, @@ -616,9 +617,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i47.Key? key; + final _i48.Key? key; - final _i47.Size prevSize; + final _i48.Size prevSize; @override String toString() { @@ -688,8 +689,8 @@ class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { class PlaylistGenerateResultRoute extends _i44.PageRouteInfo { PlaylistGenerateResultRoute({ - _i47.Key? key, - required _i48.GeneratePlaylistProviderInput state, + _i48.Key? key, + required _i49.GeneratePlaylistProviderInput state, List<_i44.PageRouteInfo>? children, }) : super( PlaylistGenerateResultRoute.name, @@ -720,9 +721,9 @@ class PlaylistGenerateResultRouteArgs { required this.state, }); - final _i47.Key? key; + final _i48.Key? key; - final _i48.GeneratePlaylistProviderInput state; + final _i49.GeneratePlaylistProviderInput state; @override String toString() { @@ -755,7 +756,7 @@ class PlaylistRoute extends _i44.PageRouteInfo { PlaylistRoute({ _i45.Key? key, required String id, - required _i46.PlaylistSimple playlist, + required _i46.SpotubeSimplePlaylistObject playlist, List<_i44.PageRouteInfo>? children, }) : super( PlaylistRoute.name, @@ -794,7 +795,7 @@ class PlaylistRouteArgs { final String id; - final _i46.PlaylistSimple playlist; + final _i46.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -1034,7 +1035,7 @@ class StatsStreamsRoute extends _i44.PageRouteInfo { /// [_i37.TrackPage] class TrackRoute extends _i44.PageRouteInfo { TrackRoute({ - _i47.Key? key, + _i48.Key? key, required String trackId, List<_i44.PageRouteInfo>? children, }) : super( @@ -1069,7 +1070,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i47.Key? key; + final _i48.Key? key; final String trackId; diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index bfb4a318..7237afc6 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -31,12 +31,12 @@ class TrackDetailsDialog extends HookWidget { textStyle: const TextStyle(color: Colors.blue), hideOverflowArtist: false, ), - context.l10n.album: LinkText( - track.album!.name!, - AlbumRoute(album: track.album!, id: track.album!.id!), - overflow: TextOverflow.ellipsis, - style: const TextStyle(color: Colors.blue), - ), + // context.l10n.album: LinkText( + // track.album!.name!, + // AlbumRoute(album: track.album!, id: track.album!.id!), + // overflow: TextOverflow.ellipsis, + // style: const TextStyle(color: Colors.blue), + // ), context.l10n.duration: (track is SourcedTrack ? (track as SourcedTrack).sourceInfo.duration : track.duration!) diff --git a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart index 47fb0f33..385c2603 100644 --- a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart +++ b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart @@ -6,6 +6,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/modules/artist/artist_card.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; @@ -99,8 +100,9 @@ class HorizontalPlaybuttonCardView extends HookWidget { return switch (item) { PlaylistSimple() => - PlaylistCard(item as PlaylistSimple), - AlbumSimple() => AlbumCard(item as AlbumSimple), + PlaylistCard(item as SpotubeSimplePlaylistObject), + AlbumSimple() => + AlbumCard(item as SpotubeSimpleAlbumObject), Artist() => ArtistCard(item as Artist), _ => const SizedBox.shrink(), }; diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index a0738165..0136c419 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -215,9 +215,9 @@ class TrackOptions extends HookConsumerWidget { onSelected: (value) async { switch (value) { case TrackOptionValue.album: - await context.navigateTo( - AlbumRoute(id: track.album!.id!, album: track.album!), - ); + // await context.navigateTo( + // AlbumRoute(id: track.album!.id!, album: track.album!), + // ); break; case TrackOptionValue.delete: await File((track as LocalTrack).path).delete(); diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index f47980cd..9d9045c5 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -258,13 +258,15 @@ class TrackTile extends HookConsumerWidget { ), _ => Align( alignment: Alignment.centerLeft, - child: LinkText( + /* child: LinkText( track.album!.name!, AlbumRoute( - album: track.album!, id: track.album!.id!), + album: track.album!, + id: track.album!.id!, + ), push: true, overflow: TextOverflow.ellipsis, - ), + ), */ ) }, ), diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart index a141a21d..fa23091e 100644 --- a/lib/hooks/configurators/use_deep_linking.dart +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -30,9 +30,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final album = await spotify.invoke((api) { return api.albums.get(url.pathSegments.last); }); - router.navigate( - AlbumRoute(id: album.id!, album: album), - ); + // router.navigate( + // AlbumRoute(id: album.id!, album: album), + // ); break; case "artist": router.navigate(ArtistRoute(artistId: url.pathSegments.last)); @@ -41,8 +41,8 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final playlist = await spotify.invoke((api) { return api.playlists.get(url.pathSegments.last); }); - router - .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); + // router + // .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); break; case "track": router.navigate(TrackRoute(trackId: url.pathSegments.last)); @@ -72,9 +72,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final album = await spotify.invoke((api) { return api.albums.get(endSegment); }); - await router.navigate( - AlbumRoute(id: album.id!, album: album), - ); + // await router.navigate( + // AlbumRoute(id: album.id!, album: album), + // ); break; case "spotify:artist": await router.navigate(ArtistRoute(artistId: endSegment)); @@ -86,9 +86,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final playlist = await spotify.invoke((api) { return api.playlists.get(endSegment); }); - await router.navigate( - PlaylistRoute(id: playlist.id!, playlist: playlist), - ); + // await router.navigate( + // PlaylistRoute(id: playlist.id!, playlist: playlist), + // ); break; default: break; diff --git a/lib/models/metadata/artist.dart b/lib/models/metadata/artist.dart index 2bee1191..89c59082 100644 --- a/lib/models/metadata/artist.dart +++ b/lib/models/metadata/artist.dart @@ -26,3 +26,15 @@ class SpotubeSimpleArtistObject with _$SpotubeSimpleArtistObject { factory SpotubeSimpleArtistObject.fromJson(Map json) => _$SpotubeSimpleArtistObjectFromJson(json); } + +extension SpotubeFullArtistObjectAsString on List { + String asString() { + return map((e) => e.name).join(", "); + } +} + +extension SpotubeSimpleArtistObjectAsString on List { + String asString() { + return map((e) => e.name).join(", "); + } +} diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart index 1fc3bb30..c058b120 100644 --- a/lib/models/metadata/image.dart +++ b/lib/models/metadata/image.dart @@ -11,3 +11,33 @@ class SpotubeImageObject with _$SpotubeImageObject { factory SpotubeImageObject.fromJson(Map json) => _$SpotubeImageObjectFromJson(json); } + +enum ImagePlaceholder { + albumArt, + artist, + collection, + online, +} + +extension SpotifyImageExtensions on List? { + String asUrlString({ + int index = 1, + required ImagePlaceholder placeholder, + }) { + final String placeholderUrl = { + ImagePlaceholder.albumArt: Assets.albumPlaceholder.path, + ImagePlaceholder.artist: Assets.userPlaceholder.path, + ImagePlaceholder.collection: Assets.placeholder.path, + ImagePlaceholder.online: + "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", + }[placeholder]!; + + final sortedImage = this?.sorted((a, b) => a.width!.compareTo(b.width!)); + + return sortedImage != null && sortedImage.isNotEmpty + ? sortedImage[ + index > sortedImage.length - 1 ? sortedImage.length - 1 : index] + .url + : placeholderUrl; + } +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 6e70ef99..c3ce0436 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -1,6 +1,9 @@ library metadata_objects; +import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/utils/primitive_utils.dart'; part 'metadata.g.dart'; part 'metadata.freezed.dart'; diff --git a/lib/models/metadata/pagination.dart b/lib/models/metadata/pagination.dart index 5055a59e..093c1d2b 100644 --- a/lib/models/metadata/pagination.dart +++ b/lib/models/metadata/pagination.dart @@ -2,20 +2,20 @@ part of 'metadata.dart'; @Freezed(genericArgumentFactories: true) class SpotubePaginationResponseObject - with _$SpotubePaginationResponseObject { + with _$SpotubePaginationResponseObject { factory SpotubePaginationResponseObject({ required int limit, required int? nextOffset, required int total, required bool hasMore, required List items, - }) = _SpotubePaginationResponseObject; + }) = _SpotubePaginationResponseObject; factory SpotubePaginationResponseObject.fromJson( Map json, T Function(Map json) fromJsonT, ) => - _$SpotubePaginationResponseObjectFromJson( + _$SpotubePaginationResponseObjectFromJson( json, (json) => fromJsonT(json as Map), ); diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index 5fee9cc4..33a7dc01 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -9,13 +9,14 @@ import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -24,7 +25,7 @@ extension FormattedAlbumType on AlbumType { } class AlbumCard extends HookConsumerWidget { - final AlbumSimple album; + final SpotubeSimpleAlbumObject album; final bool _isTile; const AlbumCard( this.album, { @@ -46,18 +47,15 @@ class AlbumCard extends HookConsumerWidget { final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); bool isPlaylistPlaying = useMemoized( - () => playlist.containsCollection(album.id!), + () => playlist.containsCollection(album.id), [playlist, album.id], ); final updating = useState(false); Future> fetchAllTrack() async { - if (album.tracks != null && album.tracks!.isNotEmpty) { - return album.tracks!.asTracks(album, ref); - } - await ref.read(albumTracksProvider(album).future); - return ref.read(albumTracksProvider(album).notifier).fetchAll(); + // return ref.read(metadataPluginAlbumTracksProvider(album).notifier).fetchAll(); + return []; } var imageUrl = album.images.asUrlString( @@ -65,11 +63,10 @@ class AlbumCard extends HookConsumerWidget { ); var isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - var description = - "${album.albumType?.formatted} • ${album.artists?.asString() ?? ""}"; + var description = "${album.albumType} • ${album.artists.asString()}"; void onTap() { - context.navigateTo(AlbumRoute(id: album.id!, album: album)); + context.navigateTo(AlbumRoute(id: album.id, album: album)); } void onPlaybuttonPressed() async { @@ -90,13 +87,13 @@ class AlbumCard extends HookConsumerWidget { await remotePlayback.load( WebSocketLoadEventData.album( tracks: fetchedTracks, - collection: album, + // collection: album, ), ); } else { await playlistNotifier.load(fetchedTracks, autoPlay: true); - playlistNotifier.addCollection(album.id!); - historyNotifier.addAlbums([album]); + playlistNotifier.addCollection(album.id); + // historyNotifier.addAlbums([album]); } } finally { updating.value = false; @@ -114,8 +111,8 @@ class AlbumCard extends HookConsumerWidget { if (fetchedTracks.isEmpty) return; playlistNotifier.addTracks(fetchedTracks); - playlistNotifier.addCollection(album.id!); - historyNotifier.addAlbums([album]); + playlistNotifier.addCollection(album.id); + // historyNotifier.addAlbums([album]); if (context.mounted) { showToast( context: context, @@ -147,7 +144,7 @@ class AlbumCard extends HookConsumerWidget { imageUrl: imageUrl, isPlaying: isPlaylistPlaying, isLoading: isLoading, - title: album.name!, + title: album.name, description: description, onTap: onTap, onPlaybuttonPressed: onPlaybuttonPressed, @@ -159,7 +156,7 @@ class AlbumCard extends HookConsumerWidget { imageUrl: imageUrl, isPlaying: isPlaylistPlaying, isLoading: isLoading, - title: album.name!, + title: album.name, description: description, onTap: onTap, onPlaybuttonPressed: onPlaybuttonPressed, diff --git a/lib/modules/home/sections/friends/friend_item.dart b/lib/modules/home/sections/friends/friend_item.dart index bf04558f..00617404 100644 --- a/lib/modules/home/sections/friends/friend_item.dart +++ b/lib/modules/home/sections/friends/friend_item.dart @@ -99,9 +99,9 @@ class FriendItem extends HookConsumerWidget { (api) => api.albums.get(friend.track.album.id), ); if (context.mounted) { - context.navigateTo( - AlbumRoute(id: album.id!, album: album), - ); + // context.navigateTo( + // AlbumRoute(id: album.id!, album: album), + // ); } }, ), diff --git a/lib/modules/home/sections/genres/genre_card_playlist_card.dart b/lib/modules/home/sections/genres/genre_card_playlist_card.dart index 328507cc..7bae4503 100644 --- a/lib/modules/home/sections/genres/genre_card_playlist_card.dart +++ b/lib/modules/home/sections/genres/genre_card_playlist_card.dart @@ -47,9 +47,9 @@ class GenreSectionCardPlaylistCard extends HookConsumerWidget { }, ), onPressed: () { - context.navigateTo( - PlaylistRoute(id: playlist.id!, playlist: playlist), - ); + // context.navigateTo( + // PlaylistRoute(id: playlist.id!, playlist: playlist), + // ); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index c4ffffa7..3d180e7c 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -9,8 +9,8 @@ import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; @@ -20,7 +20,7 @@ import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:stroke_text/stroke_text.dart'; class PlaylistCard extends HookConsumerWidget { - final PlaylistSimple playlist; + final SpotubeSimplePlaylistObject playlist; final bool _isTile; const PlaylistCard( @@ -43,7 +43,7 @@ class PlaylistCard extends HookConsumerWidget { final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; bool isPlaylistPlaying = useMemoized( - () => playlistQueue.containsCollection(playlist.id!), + () => playlistQueue.containsCollection(playlist.id), [playlistQueue, playlist.id], ); @@ -55,8 +55,7 @@ class PlaylistCard extends HookConsumerWidget { return await ref.read(likedTracksProvider.future); } - final result = - await ref.read(playlistTracksProvider(playlist.id!).future); + final result = await ref.read(playlistTracksProvider(playlist.id).future); return result.items; } @@ -68,11 +67,11 @@ class PlaylistCard extends HookConsumerWidget { return initialTracks; } - return ref.read(playlistTracksProvider(playlist.id!).notifier).fetchAll(); + return ref.read(playlistTracksProvider(playlist.id).notifier).fetchAll(); } void onTap() { - context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); + context.navigateTo(PlaylistRoute(id: playlist.id, playlist: playlist)); } void onPlaybuttonPressed() async { @@ -96,13 +95,13 @@ class PlaylistCard extends HookConsumerWidget { await remotePlayback.load( WebSocketLoadEventData.playlist( tracks: allTracks, - collection: playlist, + // collection: playlist, ), ); } else { await playlistNotifier.load(fetchedInitialTracks, autoPlay: true); - playlistNotifier.addCollection(playlist.id!); - historyNotifier.addPlaylists([playlist]); + playlistNotifier.addCollection(playlist.id); + // historyNotifier.addPlaylists([playlist]); final allTracks = await fetchAllTracks(); @@ -126,8 +125,8 @@ class PlaylistCard extends HookConsumerWidget { if (fetchedInitialTracks.isEmpty) return; playlistNotifier.addTracks(fetchedInitialTracks); - playlistNotifier.addCollection(playlist.id!); - historyNotifier.addPlaylists([playlist]); + playlistNotifier.addCollection(playlist.id); + // historyNotifier.addPlaylists([playlist]); if (context.mounted) { showToast( context: context, @@ -160,50 +159,49 @@ class PlaylistCard extends HookConsumerWidget { ); final isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - final isOwner = playlist.owner?.id == me.asData?.value.id && - me.asData?.value.id != null; + final isOwner = + playlist.owner.id == me.asData?.value.id && me.asData?.value.id != null; - final image = - playlist.owner?.displayName == "Spotify" && Env.disableSpotifyImages - ? Consumer( - builder: (context, ref, child) { - final (:color, :colorBlendMode, :src, :placement) = - ref.watch(playlistImageProvider(playlist.id!)); + final image = playlist.owner.name == "Spotify" && Env.disableSpotifyImages + ? Consumer( + builder: (context, ref, child) { + final (:color, :colorBlendMode, :src, :placement) = + ref.watch(playlistImageProvider(playlist.id)); - return Stack( - children: [ - Positioned.fill( - child: Image.asset( - src, - color: color, - colorBlendMode: colorBlendMode, - fit: BoxFit.cover, - ), + return Stack( + children: [ + Positioned.fill( + child: Image.asset( + src, + color: color, + colorBlendMode: colorBlendMode, + fit: BoxFit.cover, + ), + ), + Positioned.fill( + top: placement == Alignment.topLeft ? 10 : null, + left: 10, + bottom: placement == Alignment.bottomLeft ? 10 : null, + child: StrokeText( + text: playlist.name, + strokeColor: Colors.white, + strokeWidth: 3, + textColor: Colors.black, + textStyle: const TextStyle( + fontSize: 16, + fontStyle: FontStyle.italic, ), - Positioned.fill( - top: placement == Alignment.topLeft ? 10 : null, - left: 10, - bottom: placement == Alignment.bottomLeft ? 10 : null, - child: StrokeText( - text: playlist.name!, - strokeColor: Colors.white, - strokeWidth: 3, - textColor: Colors.black, - textStyle: const TextStyle( - fontSize: 16, - fontStyle: FontStyle.italic, - ), - ), - ), - ], - ); - }, - ) - : null; + ), + ), + ], + ); + }, + ) + : null; if (_isTile) { return PlaybuttonTile( - title: playlist.name!, + title: playlist.name, description: playlist.description, image: image, imageUrl: image == null ? imageUrl : null, @@ -217,7 +215,7 @@ class PlaylistCard extends HookConsumerWidget { } return PlaybuttonCard( - title: playlist.name!, + title: playlist.name, description: playlist.description, image: image, imageUrl: image == null ? imageUrl : null, diff --git a/lib/modules/stats/common/album_item.dart b/lib/modules/stats/common/album_item.dart index cd0a6caf..b69e1d15 100644 --- a/lib/modules/stats/common/album_item.dart +++ b/lib/modules/stats/common/album_item.dart @@ -32,19 +32,19 @@ class StatsAlbumItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text("${album.albumType?.formatted} • "), - Flexible( - child: ArtistLink( - artists: album.artists ?? [], - mainAxisAlignment: WrapAlignment.start, - onOverflowArtistClick: () => - context.navigateTo(AlbumRoute(id: album.id!, album: album)), - ), - ), + // Flexible( + // child: ArtistLink( + // artists: album.artists ?? [], + // mainAxisAlignment: WrapAlignment.start, + // onOverflowArtistClick: () => + // context.navigateTo(AlbumRoute(id: album.id!, album: album)), + // ), + // ), ], ), trailing: info, onPressed: () { - context.navigateTo(AlbumRoute(id: album.id!, album: album)); + // context.navigateTo(AlbumRoute(id: album.id!, album: album)); }, ); } diff --git a/lib/modules/stats/common/playlist_item.dart b/lib/modules/stats/common/playlist_item.dart index 58610af1..b1fdc920 100644 --- a/lib/modules/stats/common/playlist_item.dart +++ b/lib/modules/stats/common/playlist_item.dart @@ -35,7 +35,7 @@ class StatsPlaylistItem extends StatelessWidget { ), trailing: info, onPressed: () { - context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); + // context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); }, ); } diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 5773f9fa..dd57b2e3 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -2,18 +2,19 @@ import 'package:flutter/material.dart' as material; import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/library/albums.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; import 'package:spotube/provider/spotify/spotify.dart'; @RoutePage() class AlbumPage extends HookConsumerWidget { static const name = "album"; - final AlbumSimple album; + final SpotubeSimpleAlbumObject album; final String id; const AlbumPage({ super.key, @@ -23,16 +24,19 @@ class AlbumPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final tracks = ref.watch(albumTracksProvider(album)); - final tracksNotifier = ref.watch(albumTracksProvider(album).notifier); - final favoriteAlbumsNotifier = ref.watch(favoriteAlbumsProvider.notifier); - final isSavedAlbum = ref.watch(albumsIsSavedProvider(album.id!)); + final tracks = ref.watch(metadataPluginAlbumTracksProvider(album.id)); + final tracksNotifier = + ref.watch(metadataPluginAlbumTracksProvider(album.id).notifier); + final favoriteAlbumsNotifier = + ref.watch(metadataPluginSavedAlbumsProvider.notifier); + final isSavedAlbum = + ref.watch(metadataPluginIsSavedAlbumProvider(album.id)); return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(albumTracksProvider(album)); - ref.invalidate(favoriteAlbumsProvider); - ref.invalidate(albumsIsSavedProvider(album.id!)); + ref.invalidate(metadataPluginAlbumTracksProvider(album.id)); + ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id)); + ref.invalidate(metadataPluginSavedAlbumsProvider); }, child: TrackPresentation( options: TrackPresentationOptions( @@ -40,10 +44,10 @@ class AlbumPage extends HookConsumerWidget { image: album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), - title: album.name!, + title: album.name, description: - "${context.l10n.released} • ${album.releaseDate} • ${album.artists!.first.name}", - tracks: tracks.asData?.value.items ?? [], + "${context.l10n.released} • ${album.releaseDate} • ${album.artists.first.name}", + tracks: [], pagination: PaginationProps( hasNextPage: tracks.asData?.value.hasMore ?? false, isLoading: tracks.isLoading || tracks.isLoadingNextPage, @@ -51,24 +55,24 @@ class AlbumPage extends HookConsumerWidget { await tracksNotifier.fetchMore(); }, onFetchAll: () async { - return tracksNotifier.fetchAll(); + // return tracksNotifier.fetchAll(); + return []; }, onRefresh: () async { - ref.invalidate(albumTracksProvider(album)); + // ref.invalidate(albumTracksProvider(album)); }, ), routePath: "/album/${album.id}", - shareUrl: album.externalUrls?.spotify ?? - "https://open.spotify.com/album/${album.id}", + shareUrl: album.externalUri, isLiked: isSavedAlbum.asData?.value ?? false, - owner: album.artists!.first.name, + owner: album.artists.first.name, onHeart: isSavedAlbum.asData?.value == null ? null : () async { if (isSavedAlbum.asData!.value) { - await favoriteAlbumsNotifier.removeFavorites([album.id!]); + await favoriteAlbumsNotifier.removeFavorite([album]); } else { - await favoriteAlbumsNotifier.addFavorites([album.id!]); + await favoriteAlbumsNotifier.addFavorite([album]); } return null; }, diff --git a/lib/pages/home/feed/feed_section.dart b/lib/pages/home/feed/feed_section.dart index 2b38d0ed..5ece363d 100644 --- a/lib/pages/home/feed/feed_section.dart +++ b/lib/pages/home/feed/feed_section.dart @@ -43,49 +43,49 @@ class HomeFeedSectionPage extends HookConsumerWidget { child: CustomScrollView( controller: controller, slivers: [ - if (isArtist) - SliverGrid.builder( - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - mainAxisExtent: 250, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - ), - itemCount: section.items.length, - itemBuilder: (context, index) { - final item = section.items[index]; - return ArtistCard(item.artist!.asArtist); - }, - ) - else - PlaybuttonView( - controller: controller, - itemCount: section.items.length, - hasMore: false, - isLoading: false, - onRequestMore: () => {}, - listItemBuilder: (context, index) { - final item = section.items[index]; - if (item.album != null) { - return AlbumCard.tile(item.album!.asAlbum); - } - if (item.playlist != null) { - return PlaylistCard.tile(item.playlist!.asPlaylist); - } - return const SizedBox.shrink(); - }, - gridItemBuilder: (context, index) { - final item = section.items[index]; - if (item.album != null) { - return AlbumCard(item.album!.asAlbum); - } - if (item.playlist != null) { - return PlaylistCard(item.playlist!.asPlaylist); - } - return const SizedBox.shrink(); - }, - ), + // if (isArtist) + // SliverGrid.builder( + // gridDelegate: + // const SliverGridDelegateWithMaxCrossAxisExtent( + // maxCrossAxisExtent: 200, + // mainAxisExtent: 250, + // crossAxisSpacing: 8, + // mainAxisSpacing: 8, + // ), + // itemCount: section.items.length, + // itemBuilder: (context, index) { + // final item = section.items[index]; + // return ArtistCard(item.artist!.asArtist); + // }, + // ) + // else + // PlaybuttonView( + // controller: controller, + // itemCount: section.items.length, + // hasMore: false, + // isLoading: false, + // onRequestMore: () => {}, + // listItemBuilder: (context, index) { + // final item = section.items[index]; + // if (item.album != null) { + // return AlbumCard.tile(item.album!.asAlbum); + // } + // if (item.playlist != null) { + // return PlaylistCard.tile(item.playlist!.asPlaylist); + // } + // return const SizedBox.shrink(); + // }, + // gridItemBuilder: (context, index) { + // final item = section.items[index]; + // if (item.album != null) { + // return AlbumCard(item.album!.asAlbum); + // } + // if (item.playlist != null) { + // return PlaylistCard(item.playlist!.asPlaylist); + // } + // return const SizedBox.shrink(); + // }, + // ), const SliverToBoxAdapter( child: SafeArea( child: SizedBox(), diff --git a/lib/pages/home/genres/genre_playlists.dart b/lib/pages/home/genres/genre_playlists.dart index ea421cb4..bf36f834 100644 --- a/lib/pages/home/genres/genre_playlists.dart +++ b/lib/pages/home/genres/genre_playlists.dart @@ -115,25 +115,25 @@ class GenrePlaylistsPage extends HookConsumerWidget { ), ), const SliverGap(20), - SliverSafeArea( - top: false, - sliver: SliverPadding( - padding: EdgeInsets.symmetric( - horizontal: mediaQuery.mdAndDown ? 12 : 24, - ), - sliver: PlaybuttonView( - controller: scrollController, - itemCount: playlists.asData?.value.items.length ?? 0, - isLoading: playlists.isLoading, - hasMore: playlists.asData?.value.hasMore == true, - onRequestMore: playlistsNotifier.fetchMore, - listItemBuilder: (context, index) => PlaylistCard.tile( - playlists.asData!.value.items[index]), - gridItemBuilder: (context, index) => - PlaylistCard(playlists.asData!.value.items[index]), - ), - ), - ), + // SliverSafeArea( + // top: false, + // sliver: SliverPadding( + // padding: EdgeInsets.symmetric( + // horizontal: mediaQuery.mdAndDown ? 12 : 24, + // ), + // sliver: PlaybuttonView( + // controller: scrollController, + // itemCount: playlists.asData?.value.items.length ?? 0, + // isLoading: playlists.isLoading, + // hasMore: playlists.asData?.value.hasMore == true, + // onRequestMore: playlistsNotifier.fetchMore, + // listItemBuilder: (context, index) => PlaylistCard.tile( + // playlists.asData!.value.items[index]), + // gridItemBuilder: (context, index) => + // PlaylistCard(playlists.asData!.value.items[index]), + // ), + // ), + // ), const SliverGap(20), ], ), diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart index 9e6f2987..87ae9fe4 100644 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ b/lib/pages/library/playlist_generate/playlist_generate_result.dart @@ -137,14 +137,14 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { ), ); - if (playlist != null && context.mounted) { - context.navigateTo( - PlaylistRoute( - id: playlist.id!, - playlist: playlist, - ), - ); - } + // if (playlist != null && context.mounted) { + // context.navigateTo( + // PlaylistRoute( + // id: playlist.id!, + // playlist: playlist, + // ), + // ); + // } }, child: Text(context.l10n.create_a_playlist), ), diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index beaa779f..4534e531 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -15,6 +15,8 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/library/albums.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -25,9 +27,10 @@ class UserAlbumsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); - final albumsQuery = ref.watch(favoriteAlbumsProvider); - final albumsQueryNotifier = ref.watch(favoriteAlbumsProvider.notifier); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); + final albumsQuery = ref.watch(metadataPluginSavedAlbumsProvider); + final albumsQueryNotifier = + ref.watch(metadataPluginSavedAlbumsProvider.notifier); final controller = useScrollController(); @@ -39,7 +42,7 @@ class UserAlbumsPage extends HookConsumerWidget { } return albumsQuery.asData?.value.items .map((e) => ( - weightedRatio(e.name!, searchText.value), + weightedRatio(e.name, searchText.value), e, )) .sorted((a, b) => b.$1.compareTo(a.$1)) @@ -49,7 +52,7 @@ class UserAlbumsPage extends HookConsumerWidget { []; }, [albumsQuery.asData?.value, searchText.value]); - if (auth.asData?.value == null) { + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index 8b9e0dc3..d12c54b9 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -5,18 +5,20 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() @@ -28,42 +30,47 @@ class UserPlaylistsPage extends HookConsumerWidget { Widget build(BuildContext context, ref) { final searchText = useState(''); - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); - final playlistsQuery = ref.watch(favoritePlaylistsProvider); + final me = ref.watch(metadataPluginUserProvider); + final playlistsQuery = ref.watch(metadataPluginSavedPlaylistsProvider); final playlistsQueryNotifier = - ref.watch(favoritePlaylistsProvider.notifier); + ref.watch(metadataPluginSavedPlaylistsProvider.notifier); final likedTracksPlaylist = useMemoized( - () => PlaylistSimple() - ..name = context.l10n.liked_tracks - ..description = context.l10n.liked_tracks_description - ..type = "playlist" - ..collaborative = false - ..public = false - ..id = "user-liked-tracks" - ..images = [ - Image() - ..height = 300 - ..width = 300 - ..url = "assets/liked-tracks.jpg" - ], - [context.l10n], + () => me.asData?.value == null + ? null + : SpotubeSimplePlaylistObject( + id: "liked-tracks", + name: context.l10n.liked_tracks, + description: context.l10n.liked_tracks_description, + externalUri: "", + owner: me.asData!.value!, + images: [ + SpotubeImageObject( + url: Assets.likedTracks.path, + width: 300, + height: 300, + ) + ]), + [context.l10n, me.asData?.value], ); final playlists = useMemoized( () { if (searchText.value.isEmpty) { return [ - likedTracksPlaylist, - ...?playlistsQuery.asData?.value.items, + if (likedTracksPlaylist != null) likedTracksPlaylist, + ...?playlistsQuery.asData?.value.items + as List?, ]; } return [ - likedTracksPlaylist, - ...?playlistsQuery.asData?.value.items, + if (likedTracksPlaylist != null) likedTracksPlaylist, + ...?playlistsQuery.asData?.value.items + as List?, ] - .map((e) => (weightedRatio(e.name!, searchText.value), e)) + .map((e) => (weightedRatio(e.name, searchText.value), e)) .sorted((a, b) => b.$1.compareTo(a.$1)) .where((e) => e.$1 > 50) .map((e) => e.$2) @@ -74,13 +81,13 @@ class UserPlaylistsPage extends HookConsumerWidget { final controller = useScrollController(); - if (auth.asData?.value == null) { + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(favoritePlaylistsProvider); + ref.invalidate(metadataPluginSavedPlaylistsProvider); }, child: SafeArea( bottom: false, diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 5f7591ab..5575b9d8 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -12,7 +13,7 @@ import 'package:auto_route/auto_route.dart'; class LikedPlaylistPage extends HookConsumerWidget { static const name = PlaylistPage.name; - final PlaylistSimple playlist; + final SpotubeSimplePlaylistObject playlist; const LikedPlaylistPage({ super.key, required this.playlist, @@ -42,14 +43,14 @@ class LikedPlaylistPage extends HookConsumerWidget { ref.invalidate(likedTracksProvider); }, ), - title: playlist.name!, + title: playlist.name, description: playlist.description, tracks: tracks, routePath: '/playlist/${playlist.id}', isLiked: false, shareUrl: null, onHeart: null, - owner: playlist.owner?.displayName, + owner: playlist.owner.name, ), ), ); diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index 62ced353..94c07ac1 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart' as material; import 'package:collection/collection.dart'; import 'package:flutter/material.dart' hide Page; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/dialogs/prompt_dialog.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/components/track_presentation/use_is_user_playlist.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -16,22 +16,22 @@ import 'package:auto_route/auto_route.dart'; class PlaylistPage extends HookConsumerWidget { static const name = "playlist"; - final PlaylistSimple _playlist; + final SpotubeSimplePlaylistObject _playlist; final String id; const PlaylistPage({ super.key, @PathParam("id") required this.id, - required PlaylistSimple playlist, + required SpotubeSimplePlaylistObject playlist, }) : _playlist = playlist; @override Widget build(BuildContext context, ref) { final playlist = ref .watch( - favoritePlaylistsProvider.select( + metadataPluginSavedPlaylistsProvider.select( (value) => value.whenData( - (value) => - value.items.firstWhereOrNull((s) => s.id == _playlist.id), + (value) => (value.items as List) + .firstWhereOrNull((s) => s.id == _playlist.id), ), ), ) @@ -39,21 +39,21 @@ class PlaylistPage extends HookConsumerWidget { ?.value ?? _playlist; - final tracks = ref.watch(playlistTracksProvider(playlist.id!)); + final tracks = ref.watch(playlistTracksProvider(playlist.id)); final tracksNotifier = - ref.watch(playlistTracksProvider(playlist.id!).notifier); + ref.watch(playlistTracksProvider(playlist.id).notifier); final isFavoritePlaylist = - ref.watch(isFavoritePlaylistProvider(playlist.id!)); + ref.watch(isFavoritePlaylistProvider(playlist.id)); final favoritePlaylistsNotifier = - ref.watch(favoritePlaylistsProvider.notifier); + ref.watch(metadataPluginSavedPlaylistsProvider.notifier); - final isUserPlaylist = useIsUserPlaylist(ref, playlist.id!); + final isUserPlaylist = useIsUserPlaylist(ref, playlist.id); return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(playlistTracksProvider(playlist.id!)); - ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); + ref.invalidate(playlistTracksProvider(playlist.id)); + ref.invalidate(isFavoritePlaylistProvider(playlist.id)); ref.invalidate(favoritePlaylistsProvider); }, child: TrackPresentation( @@ -67,21 +67,20 @@ class PlaylistPage extends HookConsumerWidget { isLoading: tracks.isLoading || tracks.isLoadingNextPage, onFetchMore: tracksNotifier.fetchMore, onRefresh: () async { - ref.invalidate(playlistTracksProvider(playlist.id!)); + ref.invalidate(playlistTracksProvider(playlist.id)); }, onFetchAll: () async { return await tracksNotifier.fetchAll(); }, ), - title: playlist.name!, + title: playlist.name, description: playlist.description, - owner: playlist.owner?.displayName, - ownerImage: playlist.owner?.images?.lastOrNull?.url, + owner: playlist.owner.name, + ownerImage: playlist.owner.images.lastOrNull?.url, tracks: tracks.asData?.value.items ?? [], routePath: '/playlist/${playlist.id}', isLiked: isFavoritePlaylist.asData?.value ?? false, - shareUrl: playlist.externalUrls?.spotify ?? - "https://open.spotify.com/playlist/${playlist.id}", + shareUrl: playlist.externalUri, onHeart: isFavoritePlaylist.asData?.value == null ? null : () async { diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 8e6df748..9f94650b 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -142,16 +142,16 @@ class TrackPage extends HookConsumerWidget { children: [ const Icon(SpotubeIcons.album), const Gap(5), - Flexible( - child: LinkText( - track.album!.name!, - AlbumRoute( - id: track.album!.id!, - album: track.album!, - ), - push: true, - ), - ), + // Flexible( + // child: LinkText( + // track.album!.name!, + // AlbumRoute( + // id: track.album!.id!, + // album: track.album!, + // ), + // push: true, + // ), + // ), ], ), const Gap(10), diff --git a/lib/provider/metadata_plugin/library/albums.dart b/lib/provider/metadata_plugin/library/albums.dart index 1dd61b81..daa21151 100644 --- a/lib/provider/metadata_plugin/library/albums.dart +++ b/lib/provider/metadata_plugin/library/albums.dart @@ -26,8 +26,8 @@ class MetadataPluginSavedAlbumNotifier await update((state) async { (await metadataPlugin).album.save(albums.map((e) => e.id).toList()); return state.copyWith( - items: [...state.items, albums], - ) as SpotubePaginationResponseObject; + items: [...state.items, ...albums], + ); }); for (final album in albums) { @@ -42,12 +42,10 @@ class MetadataPluginSavedAlbumNotifier return state.copyWith( items: state.items .where( - (e) => - albumIds.contains((e as SpotubeSimpleAlbumObject).id) == - false, + (e) => albumIds.contains((e).id) == false, ) - .toList() as List, - ) as SpotubePaginationResponseObject; + .toList(), + ); }); for (final album in albums) { diff --git a/lib/provider/metadata_plugin/library/artists.dart b/lib/provider/metadata_plugin/library/artists.dart index 5b8de747..5d489e00 100644 --- a/lib/provider/metadata_plugin/library/artists.dart +++ b/lib/provider/metadata_plugin/library/artists.dart @@ -26,8 +26,8 @@ class MetadataPluginSavedArtistNotifier await update((state) async { (await metadataPlugin).artist.save(artists.map((e) => e.id).toList()); return state.copyWith( - items: [...state.items, artists], - ) as SpotubePaginationResponseObject; + items: [...state.items, ...artists], + ); }); for (final artist in artists) { @@ -42,12 +42,10 @@ class MetadataPluginSavedArtistNotifier return state.copyWith( items: state.items .where( - (e) => - artistIds.contains((e as SpotubeFullArtistObject).id) == - false, + (e) => artistIds.contains((e).id) == false, ) - .toList() as List, - ) as SpotubePaginationResponseObject; + .toList(), + ); }); for (final artist in artists) { diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index cbb5b166..1ebe2a63 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -36,8 +36,8 @@ class MetadataPluginSavedPlaylistsNotifier state.value!.copyWith( items: state.value!.items .map((element) => element.id == playlist.id ? playlist : element) - .toList() as List, - ) as SpotubePaginationResponseObject, + .toList(), + ), ); } @@ -46,7 +46,7 @@ class MetadataPluginSavedPlaylistsNotifier (await metadataPlugin).playlist.save(playlist.id); return state.copyWith( items: [...state.items, playlist], - ) as SpotubePaginationResponseObject; + ); }); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); @@ -56,10 +56,8 @@ class MetadataPluginSavedPlaylistsNotifier await update((state) async { (await metadataPlugin).playlist.unsave(playlist.id); return state.copyWith( - items: state.items - .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) - .toList() as List, - ) as SpotubePaginationResponseObject; + items: state.items.where((e) => (e).id != playlist.id).toList(), + ); }); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); @@ -69,10 +67,8 @@ class MetadataPluginSavedPlaylistsNotifier await update((state) async { (await metadataPlugin).playlist.deletePlaylist(playlist.id); return state.copyWith( - items: state.items - .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) - .toList() as List, - ) as SpotubePaginationResponseObject; + items: state.items.where((e) => (e).id != playlist.id).toList(), + ); }); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); diff --git a/lib/provider/metadata_plugin/utils/family_paginated.dart b/lib/provider/metadata_plugin/utils/family_paginated.dart index eb656431..a994bdab 100644 --- a/lib/provider/metadata_plugin/utils/family_paginated.dart +++ b/lib/provider/metadata_plugin/utils/family_paginated.dart @@ -2,8 +2,6 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; -// ignore: implementation_imports -import 'package:riverpod/src/async_notifier.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; abstract class FamilyPaginatedAsyncNotifier @@ -22,17 +20,20 @@ abstract class FamilyPaginatedAsyncNotifier state.value!.nextOffset!, state.value!.limit, ); - return newState.copyWith(items: [ - ...state.value!.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }, ); } Future> fetchAll() async { if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items as List; + if (!state.value!.hasMore) return state.value!.items.cast(); bool hasMore = true; while (hasMore) { @@ -43,14 +44,14 @@ abstract class FamilyPaginatedAsyncNotifier ); hasMore = newState.hasMore; - return newState.copyWith(items: [ - ...state.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + final oldItems = state.items.isEmpty ? [] : state.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }); } - return state.value!.items as List; + return state.value!.items.cast(); } } @@ -71,8 +72,8 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier state.value!.limit, ); return newState.copyWith(items: [ - ...state.value!.items as List, - ...newState.items as List, + ...state.value!.items.cast(), + ...newState.items.cast(), ]) as SpotubePaginationResponseObject; }, ); @@ -80,7 +81,7 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier Future> fetchAll() async { if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items as List; + if (!state.value!.hasMore) return state.value!.items.cast(); bool hasMore = true; while (hasMore) { @@ -92,12 +93,12 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier hasMore = newState.hasMore; return newState.copyWith(items: [ - ...state.items as List, - ...newState.items as List, + ...state.items.cast(), + ...newState.items.cast(), ]) as SpotubePaginationResponseObject; }); } - return state.value!.items as List; + return state.value!.items.cast(); } } diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart index c82e2f51..6a37929d 100644 --- a/lib/provider/metadata_plugin/utils/paginated.dart +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -22,17 +22,20 @@ mixin PaginatedAsyncNotifierMixin state.value!.nextOffset!, state.value!.limit, ); - return newState.copyWith(items: [ - ...state.value!.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }, ); } Future> fetchAll() async { if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items as List; + if (!state.value!.hasMore) return state.value!.items.cast(); bool hasMore = true; while (hasMore) { @@ -43,14 +46,14 @@ mixin PaginatedAsyncNotifierMixin ); hasMore = newState.hasMore; - return newState.copyWith(items: [ - ...state.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + final oldItems = state.items.isEmpty ? [] : state.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }); } - return state.value!.items as List; + return state.value!.items.cast(); } } diff --git a/lib/services/metadata/endpoints/album.dart b/lib/services/metadata/endpoints/album.dart index cac324d6..8a858343 100644 --- a/lib/services/metadata/endpoints/album.dart +++ b/lib/services/metadata/endpoints/album.dart @@ -33,7 +33,7 @@ class MetadataPluginAlbumEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson(json.cast()), @@ -52,7 +52,7 @@ class MetadataPluginAlbumEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeSimpleAlbumObject.fromJson(json.cast()), diff --git a/lib/services/metadata/endpoints/artist.dart b/lib/services/metadata/endpoints/artist.dart index f8ff22f8..188e9ea3 100644 --- a/lib/services/metadata/endpoints/artist.dart +++ b/lib/services/metadata/endpoints/artist.dart @@ -33,7 +33,7 @@ class MetadataPluginArtistEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson( json.cast(), @@ -55,7 +55,7 @@ class MetadataPluginArtistEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeSimpleAlbumObject.fromJson( json.cast(), diff --git a/lib/services/metadata/endpoints/playlist.dart b/lib/services/metadata/endpoints/playlist.dart index 09c36446..2a411ebd 100644 --- a/lib/services/metadata/endpoints/playlist.dart +++ b/lib/services/metadata/endpoints/playlist.dart @@ -33,7 +33,7 @@ class MetadataPluginPlaylistEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson(json.cast()), diff --git a/lib/services/metadata/endpoints/user.dart b/lib/services/metadata/endpoints/user.dart index e85f1c88..11ac3f9b 100644 --- a/lib/services/metadata/endpoints/user.dart +++ b/lib/services/metadata/endpoints/user.dart @@ -30,7 +30,7 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson(json.cast()), @@ -50,7 +50,8 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject< + SpotubeSimplePlaylistObject>.fromJson( raw.cast(), (Map json) => SpotubeSimplePlaylistObject.fromJson(json.cast()), @@ -70,7 +71,7 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeSimpleAlbumObject.fromJson(json.cast()), @@ -90,7 +91,7 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullArtistObject.fromJson(json.cast()), diff --git a/pubspec.yaml b/pubspec.yaml index 4dd74570..d221c3d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -112,7 +112,6 @@ dependencies: sliding_up_panel: ^2.0.0+1 sliver_tools: ^0.2.12 smtc_windows: ^1.0.0 - spotify: ^0.13.7 sqlite3: ^2.4.3 sqlite3_flutter_libs: ^0.5.23 stroke_text: ^0.0.2 @@ -157,6 +156,7 @@ dependencies: url: https://github.com/KRTirtho/hetu_spotube_plugin.git ref: main get_it: ^8.0.3 + spotify: ^0.13.7 dev_dependencies: build_runner: ^2.4.13