refactor: use metadata album & playlist object for card and pages

This commit is contained in:
Kingkor Roy Tirtho 2025-06-15 14:00:34 +06:00
parent 758b0bc9d9
commit 4b09f6c96b
36 changed files with 427 additions and 368 deletions

View File

@ -1,5 +1,6 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/models/database/database.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/home_feed.dart';
import 'package:spotube/models/spotify_friends.dart'; import 'package:spotube/models/spotify_friends.dart';
import 'package:spotube/provider/history/summary.dart'; import 'package:spotube/provider/history/summary.dart';
@ -64,24 +65,26 @@ abstract class FakeData {
..uri = "uri" ..uri = "uri"
..externalUrls = externalUrls; ..externalUrls = externalUrls;
static final AlbumSimple albumSimple = AlbumSimple() static final SpotubeSimpleAlbumObject albumSimple = SpotubeSimpleAlbumObject(
..id = "1" albumType: SpotubeAlbumType.album,
..albumType = AlbumType.album artists: [],
..artists = [artistSimple] externalUri: "https://example.com",
..availableMarkets = [Market.BD] id: "1",
..externalUrls = externalUrls name: "A good album",
..href = "text" releaseDate: "2021-01-01",
..images = [image] images: [
..name = "A good album" SpotubeImageObject(
..releaseDate = "2021-01-01" height: 1,
..releaseDatePrecision = DatePrecision.day width: 1,
..type = "type" url: "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg",
..uri = "uri"; )
],
);
static final Track track = Track() static final Track track = Track()
..id = "1" ..id = "1"
..artists = [artist, artist, artist] ..artists = [artist, artist, artist]
..album = albumSimple // ..album = albumSimple
..availableMarkets = [Market.BD] ..availableMarkets = [Market.BD]
..discNumber = 1 ..discNumber = 1
..durationMs = 50000 ..durationMs = 50000

View File

@ -10,9 +10,10 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i44; import 'package:auto_route/auto_route.dart' as _i44;
import 'package:flutter/material.dart' as _i45; import 'package:flutter/material.dart' as _i45;
import 'package:shadcn_flutter/shadcn_flutter.dart' as _i47; import 'package:shadcn_flutter/shadcn_flutter.dart' as _i48;
import 'package:spotify/spotify.dart' as _i46; import 'package:spotify/spotify.dart' as _i47;
import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i48; 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/album/album.dart' as _i2;
import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/artist/artist.dart' as _i3;
import 'package:spotube/pages/connect/connect.dart' as _i6; import 'package:spotube/pages/connect/connect.dart' as _i6;
@ -86,7 +87,7 @@ class AlbumRoute extends _i44.PageRouteInfo<AlbumRouteArgs> {
AlbumRoute({ AlbumRoute({
_i45.Key? key, _i45.Key? key,
required String id, required String id,
required _i46.AlbumSimple album, required _i46.SpotubeSimpleAlbumObject album,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
AlbumRoute.name, AlbumRoute.name,
@ -125,7 +126,7 @@ class AlbumRouteArgs {
final String id; final String id;
final _i46.AlbumSimple album; final _i46.SpotubeSimpleAlbumObject album;
@override @override
String toString() { String toString() {
@ -264,7 +265,7 @@ class GenrePlaylistsRoute extends _i44.PageRouteInfo<GenrePlaylistsRouteArgs> {
GenrePlaylistsRoute({ GenrePlaylistsRoute({
_i45.Key? key, _i45.Key? key,
required String id, required String id,
required _i46.Category category, required _i47.Category category,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
GenrePlaylistsRoute.name, GenrePlaylistsRoute.name,
@ -303,7 +304,7 @@ class GenrePlaylistsRouteArgs {
final String id; final String id;
final _i46.Category category; final _i47.Category category;
@override @override
String toString() { String toString() {
@ -335,7 +336,7 @@ class GettingStartedRoute extends _i44.PageRouteInfo<void> {
class HomeFeedSectionRoute class HomeFeedSectionRoute
extends _i44.PageRouteInfo<HomeFeedSectionRouteArgs> { extends _i44.PageRouteInfo<HomeFeedSectionRouteArgs> {
HomeFeedSectionRoute({ HomeFeedSectionRoute({
_i47.Key? key, _i48.Key? key,
required String sectionUri, required String sectionUri,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
@ -371,7 +372,7 @@ class HomeFeedSectionRouteArgs {
required this.sectionUri, required this.sectionUri,
}); });
final _i47.Key? key; final _i48.Key? key;
final String sectionUri; final String sectionUri;
@ -443,7 +444,7 @@ class LibraryRoute extends _i44.PageRouteInfo<void> {
class LikedPlaylistRoute extends _i44.PageRouteInfo<LikedPlaylistRouteArgs> { class LikedPlaylistRoute extends _i44.PageRouteInfo<LikedPlaylistRouteArgs> {
LikedPlaylistRoute({ LikedPlaylistRoute({
_i45.Key? key, _i45.Key? key,
required _i46.PlaylistSimple playlist, required _i46.SpotubeSimplePlaylistObject playlist,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
LikedPlaylistRoute.name, LikedPlaylistRoute.name,
@ -476,7 +477,7 @@ class LikedPlaylistRouteArgs {
final _i45.Key? key; final _i45.Key? key;
final _i46.PlaylistSimple playlist; final _i46.SpotubeSimplePlaylistObject playlist;
@override @override
String toString() { String toString() {
@ -584,8 +585,8 @@ class LyricsRoute extends _i44.PageRouteInfo<void> {
/// [_i18.MiniLyricsPage] /// [_i18.MiniLyricsPage]
class MiniLyricsRoute extends _i44.PageRouteInfo<MiniLyricsRouteArgs> { class MiniLyricsRoute extends _i44.PageRouteInfo<MiniLyricsRouteArgs> {
MiniLyricsRoute({ MiniLyricsRoute({
_i47.Key? key, _i48.Key? key,
required _i47.Size prevSize, required _i48.Size prevSize,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
MiniLyricsRoute.name, MiniLyricsRoute.name,
@ -616,9 +617,9 @@ class MiniLyricsRouteArgs {
required this.prevSize, required this.prevSize,
}); });
final _i47.Key? key; final _i48.Key? key;
final _i47.Size prevSize; final _i48.Size prevSize;
@override @override
String toString() { String toString() {
@ -688,8 +689,8 @@ class PlayerTrackSourcesRoute extends _i44.PageRouteInfo<void> {
class PlaylistGenerateResultRoute class PlaylistGenerateResultRoute
extends _i44.PageRouteInfo<PlaylistGenerateResultRouteArgs> { extends _i44.PageRouteInfo<PlaylistGenerateResultRouteArgs> {
PlaylistGenerateResultRoute({ PlaylistGenerateResultRoute({
_i47.Key? key, _i48.Key? key,
required _i48.GeneratePlaylistProviderInput state, required _i49.GeneratePlaylistProviderInput state,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
PlaylistGenerateResultRoute.name, PlaylistGenerateResultRoute.name,
@ -720,9 +721,9 @@ class PlaylistGenerateResultRouteArgs {
required this.state, required this.state,
}); });
final _i47.Key? key; final _i48.Key? key;
final _i48.GeneratePlaylistProviderInput state; final _i49.GeneratePlaylistProviderInput state;
@override @override
String toString() { String toString() {
@ -755,7 +756,7 @@ class PlaylistRoute extends _i44.PageRouteInfo<PlaylistRouteArgs> {
PlaylistRoute({ PlaylistRoute({
_i45.Key? key, _i45.Key? key,
required String id, required String id,
required _i46.PlaylistSimple playlist, required _i46.SpotubeSimplePlaylistObject playlist,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
PlaylistRoute.name, PlaylistRoute.name,
@ -794,7 +795,7 @@ class PlaylistRouteArgs {
final String id; final String id;
final _i46.PlaylistSimple playlist; final _i46.SpotubeSimplePlaylistObject playlist;
@override @override
String toString() { String toString() {
@ -1034,7 +1035,7 @@ class StatsStreamsRoute extends _i44.PageRouteInfo<void> {
/// [_i37.TrackPage] /// [_i37.TrackPage]
class TrackRoute extends _i44.PageRouteInfo<TrackRouteArgs> { class TrackRoute extends _i44.PageRouteInfo<TrackRouteArgs> {
TrackRoute({ TrackRoute({
_i47.Key? key, _i48.Key? key,
required String trackId, required String trackId,
List<_i44.PageRouteInfo>? children, List<_i44.PageRouteInfo>? children,
}) : super( }) : super(
@ -1069,7 +1070,7 @@ class TrackRouteArgs {
required this.trackId, required this.trackId,
}); });
final _i47.Key? key; final _i48.Key? key;
final String trackId; final String trackId;

View File

@ -31,12 +31,12 @@ class TrackDetailsDialog extends HookWidget {
textStyle: const TextStyle(color: Colors.blue), textStyle: const TextStyle(color: Colors.blue),
hideOverflowArtist: false, hideOverflowArtist: false,
), ),
context.l10n.album: LinkText( // context.l10n.album: LinkText(
track.album!.name!, // track.album!.name!,
AlbumRoute(album: track.album!, id: track.album!.id!), // AlbumRoute(album: track.album!, id: track.album!.id!),
overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.blue), // style: const TextStyle(color: Colors.blue),
), // ),
context.l10n.duration: (track is SourcedTrack context.l10n.duration: (track is SourcedTrack
? (track as SourcedTrack).sourceInfo.duration ? (track as SourcedTrack).sourceInfo.duration
: track.duration!) : track.duration!)

View File

@ -6,6 +6,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/collections/fake.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/album/album_card.dart';
import 'package:spotube/modules/artist/artist_card.dart'; import 'package:spotube/modules/artist/artist_card.dart';
import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/modules/playlist/playlist_card.dart';
@ -99,8 +100,9 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
return switch (item) { return switch (item) {
PlaylistSimple() => PlaylistSimple() =>
PlaylistCard(item as PlaylistSimple), PlaylistCard(item as SpotubeSimplePlaylistObject),
AlbumSimple() => AlbumCard(item as AlbumSimple), AlbumSimple() =>
AlbumCard(item as SpotubeSimpleAlbumObject),
Artist() => ArtistCard(item as Artist), Artist() => ArtistCard(item as Artist),
_ => const SizedBox.shrink(), _ => const SizedBox.shrink(),
}; };

View File

@ -215,9 +215,9 @@ class TrackOptions extends HookConsumerWidget {
onSelected: (value) async { onSelected: (value) async {
switch (value) { switch (value) {
case TrackOptionValue.album: case TrackOptionValue.album:
await context.navigateTo( // await context.navigateTo(
AlbumRoute(id: track.album!.id!, album: track.album!), // AlbumRoute(id: track.album!.id!, album: track.album!),
); // );
break; break;
case TrackOptionValue.delete: case TrackOptionValue.delete:
await File((track as LocalTrack).path).delete(); await File((track as LocalTrack).path).delete();

View File

@ -258,13 +258,15 @@ class TrackTile extends HookConsumerWidget {
), ),
_ => Align( _ => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: LinkText( /* child: LinkText(
track.album!.name!, track.album!.name!,
AlbumRoute( AlbumRoute(
album: track.album!, id: track.album!.id!), album: track.album!,
id: track.album!.id!,
),
push: true, push: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ), */
) )
}, },
), ),

View File

@ -30,9 +30,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
final album = await spotify.invoke((api) { final album = await spotify.invoke((api) {
return api.albums.get(url.pathSegments.last); return api.albums.get(url.pathSegments.last);
}); });
router.navigate( // router.navigate(
AlbumRoute(id: album.id!, album: album), // AlbumRoute(id: album.id!, album: album),
); // );
break; break;
case "artist": case "artist":
router.navigate(ArtistRoute(artistId: url.pathSegments.last)); router.navigate(ArtistRoute(artistId: url.pathSegments.last));
@ -41,8 +41,8 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
final playlist = await spotify.invoke((api) { final playlist = await spotify.invoke((api) {
return api.playlists.get(url.pathSegments.last); return api.playlists.get(url.pathSegments.last);
}); });
router // router
.navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); // .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist));
break; break;
case "track": case "track":
router.navigate(TrackRoute(trackId: url.pathSegments.last)); router.navigate(TrackRoute(trackId: url.pathSegments.last));
@ -72,9 +72,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
final album = await spotify.invoke((api) { final album = await spotify.invoke((api) {
return api.albums.get(endSegment); return api.albums.get(endSegment);
}); });
await router.navigate( // await router.navigate(
AlbumRoute(id: album.id!, album: album), // AlbumRoute(id: album.id!, album: album),
); // );
break; break;
case "spotify:artist": case "spotify:artist":
await router.navigate(ArtistRoute(artistId: endSegment)); await router.navigate(ArtistRoute(artistId: endSegment));
@ -86,9 +86,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
final playlist = await spotify.invoke((api) { final playlist = await spotify.invoke((api) {
return api.playlists.get(endSegment); return api.playlists.get(endSegment);
}); });
await router.navigate( // await router.navigate(
PlaylistRoute(id: playlist.id!, playlist: playlist), // PlaylistRoute(id: playlist.id!, playlist: playlist),
); // );
break; break;
default: default:
break; break;

View File

@ -26,3 +26,15 @@ class SpotubeSimpleArtistObject with _$SpotubeSimpleArtistObject {
factory SpotubeSimpleArtistObject.fromJson(Map<String, dynamic> json) => factory SpotubeSimpleArtistObject.fromJson(Map<String, dynamic> json) =>
_$SpotubeSimpleArtistObjectFromJson(json); _$SpotubeSimpleArtistObjectFromJson(json);
} }
extension SpotubeFullArtistObjectAsString on List<SpotubeFullArtistObject> {
String asString() {
return map((e) => e.name).join(", ");
}
}
extension SpotubeSimpleArtistObjectAsString on List<SpotubeSimpleArtistObject> {
String asString() {
return map((e) => e.name).join(", ");
}
}

View File

@ -11,3 +11,33 @@ class SpotubeImageObject with _$SpotubeImageObject {
factory SpotubeImageObject.fromJson(Map<String, dynamic> json) => factory SpotubeImageObject.fromJson(Map<String, dynamic> json) =>
_$SpotubeImageObjectFromJson(json); _$SpotubeImageObjectFromJson(json);
} }
enum ImagePlaceholder {
albumArt,
artist,
collection,
online,
}
extension SpotifyImageExtensions on List<SpotubeImageObject>? {
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;
}
}

View File

@ -1,6 +1,9 @@
library metadata_objects; library metadata_objects;
import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.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.g.dart';
part 'metadata.freezed.dart'; part 'metadata.freezed.dart';

View File

@ -2,20 +2,20 @@ part of 'metadata.dart';
@Freezed(genericArgumentFactories: true) @Freezed(genericArgumentFactories: true)
class SpotubePaginationResponseObject<T> class SpotubePaginationResponseObject<T>
with _$SpotubePaginationResponseObject { with _$SpotubePaginationResponseObject<T> {
factory SpotubePaginationResponseObject({ factory SpotubePaginationResponseObject({
required int limit, required int limit,
required int? nextOffset, required int? nextOffset,
required int total, required int total,
required bool hasMore, required bool hasMore,
required List<T> items, required List<T> items,
}) = _SpotubePaginationResponseObject; }) = _SpotubePaginationResponseObject<T>;
factory SpotubePaginationResponseObject.fromJson( factory SpotubePaginationResponseObject.fromJson(
Map<String, Object?> json, Map<String, Object?> json,
T Function(Map<String, dynamic> json) fromJsonT, T Function(Map<String, dynamic> json) fromJsonT,
) => ) =>
_$SpotubePaginationResponseObjectFromJson( _$SpotubePaginationResponseObjectFromJson<T>(
json, json,
(json) => fromJsonT(json as Map<String, dynamic>), (json) => fromJsonT(json as Map<String, dynamic>),
); );

View File

@ -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/components/playbutton_view/playbutton_tile.dart';
import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/extensions/track.dart'; import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/connect/connect.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/audio_player/querying_track_info.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/audio_player/audio_player.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/provider/spotify/spotify.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
@ -24,7 +25,7 @@ extension FormattedAlbumType on AlbumType {
} }
class AlbumCard extends HookConsumerWidget { class AlbumCard extends HookConsumerWidget {
final AlbumSimple album; final SpotubeSimpleAlbumObject album;
final bool _isTile; final bool _isTile;
const AlbumCard( const AlbumCard(
this.album, { this.album, {
@ -46,18 +47,15 @@ class AlbumCard extends HookConsumerWidget {
final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider);
bool isPlaylistPlaying = useMemoized( bool isPlaylistPlaying = useMemoized(
() => playlist.containsCollection(album.id!), () => playlist.containsCollection(album.id),
[playlist, album.id], [playlist, album.id],
); );
final updating = useState(false); final updating = useState(false);
Future<List<Track>> fetchAllTrack() async { Future<List<Track>> fetchAllTrack() async {
if (album.tracks != null && album.tracks!.isNotEmpty) { // return ref.read(metadataPluginAlbumTracksProvider(album).notifier).fetchAll();
return album.tracks!.asTracks(album, ref); return [];
}
await ref.read(albumTracksProvider(album).future);
return ref.read(albumTracksProvider(album).notifier).fetchAll();
} }
var imageUrl = album.images.asUrlString( var imageUrl = album.images.asUrlString(
@ -65,11 +63,10 @@ class AlbumCard extends HookConsumerWidget {
); );
var isLoading = var isLoading =
(isPlaylistPlaying && isFetchingActiveTrack) || updating.value; (isPlaylistPlaying && isFetchingActiveTrack) || updating.value;
var description = var description = "${album.albumType}${album.artists.asString()}";
"${album.albumType?.formatted}${album.artists?.asString() ?? ""}";
void onTap() { void onTap() {
context.navigateTo(AlbumRoute(id: album.id!, album: album)); context.navigateTo(AlbumRoute(id: album.id, album: album));
} }
void onPlaybuttonPressed() async { void onPlaybuttonPressed() async {
@ -90,13 +87,13 @@ class AlbumCard extends HookConsumerWidget {
await remotePlayback.load( await remotePlayback.load(
WebSocketLoadEventData.album( WebSocketLoadEventData.album(
tracks: fetchedTracks, tracks: fetchedTracks,
collection: album, // collection: album,
), ),
); );
} else { } else {
await playlistNotifier.load(fetchedTracks, autoPlay: true); await playlistNotifier.load(fetchedTracks, autoPlay: true);
playlistNotifier.addCollection(album.id!); playlistNotifier.addCollection(album.id);
historyNotifier.addAlbums([album]); // historyNotifier.addAlbums([album]);
} }
} finally { } finally {
updating.value = false; updating.value = false;
@ -114,8 +111,8 @@ class AlbumCard extends HookConsumerWidget {
if (fetchedTracks.isEmpty) return; if (fetchedTracks.isEmpty) return;
playlistNotifier.addTracks(fetchedTracks); playlistNotifier.addTracks(fetchedTracks);
playlistNotifier.addCollection(album.id!); playlistNotifier.addCollection(album.id);
historyNotifier.addAlbums([album]); // historyNotifier.addAlbums([album]);
if (context.mounted) { if (context.mounted) {
showToast( showToast(
context: context, context: context,
@ -147,7 +144,7 @@ class AlbumCard extends HookConsumerWidget {
imageUrl: imageUrl, imageUrl: imageUrl,
isPlaying: isPlaylistPlaying, isPlaying: isPlaylistPlaying,
isLoading: isLoading, isLoading: isLoading,
title: album.name!, title: album.name,
description: description, description: description,
onTap: onTap, onTap: onTap,
onPlaybuttonPressed: onPlaybuttonPressed, onPlaybuttonPressed: onPlaybuttonPressed,
@ -159,7 +156,7 @@ class AlbumCard extends HookConsumerWidget {
imageUrl: imageUrl, imageUrl: imageUrl,
isPlaying: isPlaylistPlaying, isPlaying: isPlaylistPlaying,
isLoading: isLoading, isLoading: isLoading,
title: album.name!, title: album.name,
description: description, description: description,
onTap: onTap, onTap: onTap,
onPlaybuttonPressed: onPlaybuttonPressed, onPlaybuttonPressed: onPlaybuttonPressed,

View File

@ -99,9 +99,9 @@ class FriendItem extends HookConsumerWidget {
(api) => api.albums.get(friend.track.album.id), (api) => api.albums.get(friend.track.album.id),
); );
if (context.mounted) { if (context.mounted) {
context.navigateTo( // context.navigateTo(
AlbumRoute(id: album.id!, album: album), // AlbumRoute(id: album.id!, album: album),
); // );
} }
}, },
), ),

View File

@ -47,9 +47,9 @@ class GenreSectionCardPlaylistCard extends HookConsumerWidget {
}, },
), ),
onPressed: () { onPressed: () {
context.navigateTo( // context.navigateTo(
PlaylistRoute(id: playlist.id!, playlist: playlist), // PlaylistRoute(id: playlist.id!, playlist: playlist),
); // );
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -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_card.dart';
import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/connect/connect.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/audio_player/querying_track_info.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.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'; import 'package:stroke_text/stroke_text.dart';
class PlaylistCard extends HookConsumerWidget { class PlaylistCard extends HookConsumerWidget {
final PlaylistSimple playlist; final SpotubeSimplePlaylistObject playlist;
final bool _isTile; final bool _isTile;
const PlaylistCard( const PlaylistCard(
@ -43,7 +43,7 @@ class PlaylistCard extends HookConsumerWidget {
final playing = final playing =
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
bool isPlaylistPlaying = useMemoized( bool isPlaylistPlaying = useMemoized(
() => playlistQueue.containsCollection(playlist.id!), () => playlistQueue.containsCollection(playlist.id),
[playlistQueue, playlist.id], [playlistQueue, playlist.id],
); );
@ -55,8 +55,7 @@ class PlaylistCard extends HookConsumerWidget {
return await ref.read(likedTracksProvider.future); return await ref.read(likedTracksProvider.future);
} }
final result = final result = await ref.read(playlistTracksProvider(playlist.id).future);
await ref.read(playlistTracksProvider(playlist.id!).future);
return result.items; return result.items;
} }
@ -68,11 +67,11 @@ class PlaylistCard extends HookConsumerWidget {
return initialTracks; return initialTracks;
} }
return ref.read(playlistTracksProvider(playlist.id!).notifier).fetchAll(); return ref.read(playlistTracksProvider(playlist.id).notifier).fetchAll();
} }
void onTap() { void onTap() {
context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); context.navigateTo(PlaylistRoute(id: playlist.id, playlist: playlist));
} }
void onPlaybuttonPressed() async { void onPlaybuttonPressed() async {
@ -96,13 +95,13 @@ class PlaylistCard extends HookConsumerWidget {
await remotePlayback.load( await remotePlayback.load(
WebSocketLoadEventData.playlist( WebSocketLoadEventData.playlist(
tracks: allTracks, tracks: allTracks,
collection: playlist, // collection: playlist,
), ),
); );
} else { } else {
await playlistNotifier.load(fetchedInitialTracks, autoPlay: true); await playlistNotifier.load(fetchedInitialTracks, autoPlay: true);
playlistNotifier.addCollection(playlist.id!); playlistNotifier.addCollection(playlist.id);
historyNotifier.addPlaylists([playlist]); // historyNotifier.addPlaylists([playlist]);
final allTracks = await fetchAllTracks(); final allTracks = await fetchAllTracks();
@ -126,8 +125,8 @@ class PlaylistCard extends HookConsumerWidget {
if (fetchedInitialTracks.isEmpty) return; if (fetchedInitialTracks.isEmpty) return;
playlistNotifier.addTracks(fetchedInitialTracks); playlistNotifier.addTracks(fetchedInitialTracks);
playlistNotifier.addCollection(playlist.id!); playlistNotifier.addCollection(playlist.id);
historyNotifier.addPlaylists([playlist]); // historyNotifier.addPlaylists([playlist]);
if (context.mounted) { if (context.mounted) {
showToast( showToast(
context: context, context: context,
@ -160,50 +159,49 @@ class PlaylistCard extends HookConsumerWidget {
); );
final isLoading = final isLoading =
(isPlaylistPlaying && isFetchingActiveTrack) || updating.value; (isPlaylistPlaying && isFetchingActiveTrack) || updating.value;
final isOwner = playlist.owner?.id == me.asData?.value.id && final isOwner =
me.asData?.value.id != null; playlist.owner.id == me.asData?.value.id && me.asData?.value.id != null;
final image = final image = playlist.owner.name == "Spotify" && Env.disableSpotifyImages
playlist.owner?.displayName == "Spotify" && Env.disableSpotifyImages ? Consumer(
? Consumer( builder: (context, ref, child) {
builder: (context, ref, child) { final (:color, :colorBlendMode, :src, :placement) =
final (:color, :colorBlendMode, :src, :placement) = ref.watch(playlistImageProvider(playlist.id));
ref.watch(playlistImageProvider(playlist.id!));
return Stack( return Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: Image.asset( child: Image.asset(
src, src,
color: color, color: color,
colorBlendMode: colorBlendMode, colorBlendMode: colorBlendMode,
fit: BoxFit.cover, 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, : null;
strokeWidth: 3,
textColor: Colors.black,
textStyle: const TextStyle(
fontSize: 16,
fontStyle: FontStyle.italic,
),
),
),
],
);
},
)
: null;
if (_isTile) { if (_isTile) {
return PlaybuttonTile( return PlaybuttonTile(
title: playlist.name!, title: playlist.name,
description: playlist.description, description: playlist.description,
image: image, image: image,
imageUrl: image == null ? imageUrl : null, imageUrl: image == null ? imageUrl : null,
@ -217,7 +215,7 @@ class PlaylistCard extends HookConsumerWidget {
} }
return PlaybuttonCard( return PlaybuttonCard(
title: playlist.name!, title: playlist.name,
description: playlist.description, description: playlist.description,
image: image, image: image,
imageUrl: image == null ? imageUrl : null, imageUrl: image == null ? imageUrl : null,

View File

@ -32,19 +32,19 @@ class StatsAlbumItem extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text("${album.albumType?.formatted}"), Text("${album.albumType?.formatted}"),
Flexible( // Flexible(
child: ArtistLink( // child: ArtistLink(
artists: album.artists ?? [], // artists: album.artists ?? [],
mainAxisAlignment: WrapAlignment.start, // mainAxisAlignment: WrapAlignment.start,
onOverflowArtistClick: () => // onOverflowArtistClick: () =>
context.navigateTo(AlbumRoute(id: album.id!, album: album)), // context.navigateTo(AlbumRoute(id: album.id!, album: album)),
), // ),
), // ),
], ],
), ),
trailing: info, trailing: info,
onPressed: () { onPressed: () {
context.navigateTo(AlbumRoute(id: album.id!, album: album)); // context.navigateTo(AlbumRoute(id: album.id!, album: album));
}, },
); );
} }

View File

@ -35,7 +35,7 @@ class StatsPlaylistItem extends StatelessWidget {
), ),
trailing: info, trailing: info,
onPressed: () { onPressed: () {
context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); // context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist));
}, },
); );
} }

View File

@ -2,18 +2,19 @@ import 'package:flutter/material.dart' as material;
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.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/presentation_props.dart';
import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart';
import 'package:spotube/extensions/context.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'; import 'package:spotube/provider/spotify/spotify.dart';
@RoutePage() @RoutePage()
class AlbumPage extends HookConsumerWidget { class AlbumPage extends HookConsumerWidget {
static const name = "album"; static const name = "album";
final AlbumSimple album; final SpotubeSimpleAlbumObject album;
final String id; final String id;
const AlbumPage({ const AlbumPage({
super.key, super.key,
@ -23,16 +24,19 @@ class AlbumPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final tracks = ref.watch(albumTracksProvider(album)); final tracks = ref.watch(metadataPluginAlbumTracksProvider(album.id));
final tracksNotifier = ref.watch(albumTracksProvider(album).notifier); final tracksNotifier =
final favoriteAlbumsNotifier = ref.watch(favoriteAlbumsProvider.notifier); ref.watch(metadataPluginAlbumTracksProvider(album.id).notifier);
final isSavedAlbum = ref.watch(albumsIsSavedProvider(album.id!)); final favoriteAlbumsNotifier =
ref.watch(metadataPluginSavedAlbumsProvider.notifier);
final isSavedAlbum =
ref.watch(metadataPluginIsSavedAlbumProvider(album.id));
return material.RefreshIndicator.adaptive( return material.RefreshIndicator.adaptive(
onRefresh: () async { onRefresh: () async {
ref.invalidate(albumTracksProvider(album)); ref.invalidate(metadataPluginAlbumTracksProvider(album.id));
ref.invalidate(favoriteAlbumsProvider); ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id));
ref.invalidate(albumsIsSavedProvider(album.id!)); ref.invalidate(metadataPluginSavedAlbumsProvider);
}, },
child: TrackPresentation( child: TrackPresentation(
options: TrackPresentationOptions( options: TrackPresentationOptions(
@ -40,10 +44,10 @@ class AlbumPage extends HookConsumerWidget {
image: album.images.asUrlString( image: album.images.asUrlString(
placeholder: ImagePlaceholder.albumArt, placeholder: ImagePlaceholder.albumArt,
), ),
title: album.name!, title: album.name,
description: description:
"${context.l10n.released}${album.releaseDate}${album.artists!.first.name}", "${context.l10n.released}${album.releaseDate}${album.artists.first.name}",
tracks: tracks.asData?.value.items ?? [], tracks: [],
pagination: PaginationProps( pagination: PaginationProps(
hasNextPage: tracks.asData?.value.hasMore ?? false, hasNextPage: tracks.asData?.value.hasMore ?? false,
isLoading: tracks.isLoading || tracks.isLoadingNextPage, isLoading: tracks.isLoading || tracks.isLoadingNextPage,
@ -51,24 +55,24 @@ class AlbumPage extends HookConsumerWidget {
await tracksNotifier.fetchMore(); await tracksNotifier.fetchMore();
}, },
onFetchAll: () async { onFetchAll: () async {
return tracksNotifier.fetchAll(); // return tracksNotifier.fetchAll();
return [];
}, },
onRefresh: () async { onRefresh: () async {
ref.invalidate(albumTracksProvider(album)); // ref.invalidate(albumTracksProvider(album));
}, },
), ),
routePath: "/album/${album.id}", routePath: "/album/${album.id}",
shareUrl: album.externalUrls?.spotify ?? shareUrl: album.externalUri,
"https://open.spotify.com/album/${album.id}",
isLiked: isSavedAlbum.asData?.value ?? false, isLiked: isSavedAlbum.asData?.value ?? false,
owner: album.artists!.first.name, owner: album.artists.first.name,
onHeart: isSavedAlbum.asData?.value == null onHeart: isSavedAlbum.asData?.value == null
? null ? null
: () async { : () async {
if (isSavedAlbum.asData!.value) { if (isSavedAlbum.asData!.value) {
await favoriteAlbumsNotifier.removeFavorites([album.id!]); await favoriteAlbumsNotifier.removeFavorite([album]);
} else { } else {
await favoriteAlbumsNotifier.addFavorites([album.id!]); await favoriteAlbumsNotifier.addFavorite([album]);
} }
return null; return null;
}, },

View File

@ -43,49 +43,49 @@ class HomeFeedSectionPage extends HookConsumerWidget {
child: CustomScrollView( child: CustomScrollView(
controller: controller, controller: controller,
slivers: [ slivers: [
if (isArtist) // if (isArtist)
SliverGrid.builder( // SliverGrid.builder(
gridDelegate: // gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent( // const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200, // maxCrossAxisExtent: 200,
mainAxisExtent: 250, // mainAxisExtent: 250,
crossAxisSpacing: 8, // crossAxisSpacing: 8,
mainAxisSpacing: 8, // mainAxisSpacing: 8,
), // ),
itemCount: section.items.length, // itemCount: section.items.length,
itemBuilder: (context, index) { // itemBuilder: (context, index) {
final item = section.items[index]; // final item = section.items[index];
return ArtistCard(item.artist!.asArtist); // return ArtistCard(item.artist!.asArtist);
}, // },
) // )
else // else
PlaybuttonView( // PlaybuttonView(
controller: controller, // controller: controller,
itemCount: section.items.length, // itemCount: section.items.length,
hasMore: false, // hasMore: false,
isLoading: false, // isLoading: false,
onRequestMore: () => {}, // onRequestMore: () => {},
listItemBuilder: (context, index) { // listItemBuilder: (context, index) {
final item = section.items[index]; // final item = section.items[index];
if (item.album != null) { // if (item.album != null) {
return AlbumCard.tile(item.album!.asAlbum); // return AlbumCard.tile(item.album!.asAlbum);
} // }
if (item.playlist != null) { // if (item.playlist != null) {
return PlaylistCard.tile(item.playlist!.asPlaylist); // return PlaylistCard.tile(item.playlist!.asPlaylist);
} // }
return const SizedBox.shrink(); // return const SizedBox.shrink();
}, // },
gridItemBuilder: (context, index) { // gridItemBuilder: (context, index) {
final item = section.items[index]; // final item = section.items[index];
if (item.album != null) { // if (item.album != null) {
return AlbumCard(item.album!.asAlbum); // return AlbumCard(item.album!.asAlbum);
} // }
if (item.playlist != null) { // if (item.playlist != null) {
return PlaylistCard(item.playlist!.asPlaylist); // return PlaylistCard(item.playlist!.asPlaylist);
} // }
return const SizedBox.shrink(); // return const SizedBox.shrink();
}, // },
), // ),
const SliverToBoxAdapter( const SliverToBoxAdapter(
child: SafeArea( child: SafeArea(
child: SizedBox(), child: SizedBox(),

View File

@ -115,25 +115,25 @@ class GenrePlaylistsPage extends HookConsumerWidget {
), ),
), ),
const SliverGap(20), const SliverGap(20),
SliverSafeArea( // SliverSafeArea(
top: false, // top: false,
sliver: SliverPadding( // sliver: SliverPadding(
padding: EdgeInsets.symmetric( // padding: EdgeInsets.symmetric(
horizontal: mediaQuery.mdAndDown ? 12 : 24, // horizontal: mediaQuery.mdAndDown ? 12 : 24,
), // ),
sliver: PlaybuttonView( // sliver: PlaybuttonView(
controller: scrollController, // controller: scrollController,
itemCount: playlists.asData?.value.items.length ?? 0, // itemCount: playlists.asData?.value.items.length ?? 0,
isLoading: playlists.isLoading, // isLoading: playlists.isLoading,
hasMore: playlists.asData?.value.hasMore == true, // hasMore: playlists.asData?.value.hasMore == true,
onRequestMore: playlistsNotifier.fetchMore, // onRequestMore: playlistsNotifier.fetchMore,
listItemBuilder: (context, index) => PlaylistCard.tile( // listItemBuilder: (context, index) => PlaylistCard.tile(
playlists.asData!.value.items[index]), // playlists.asData!.value.items[index]),
gridItemBuilder: (context, index) => // gridItemBuilder: (context, index) =>
PlaylistCard(playlists.asData!.value.items[index]), // PlaylistCard(playlists.asData!.value.items[index]),
), // ),
), // ),
), // ),
const SliverGap(20), const SliverGap(20),
], ],
), ),

View File

@ -137,14 +137,14 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
), ),
); );
if (playlist != null && context.mounted) { // if (playlist != null && context.mounted) {
context.navigateTo( // context.navigateTo(
PlaylistRoute( // PlaylistRoute(
id: playlist.id!, // id: playlist.id!,
playlist: playlist, // playlist: playlist,
), // ),
); // );
} // }
}, },
child: Text(context.l10n.create_a_playlist), child: Text(context.l10n.create_a_playlist),
), ),

View File

@ -15,6 +15,8 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication/authentication.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:spotube/provider/spotify/spotify.dart';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@ -25,9 +27,10 @@ class UserAlbumsPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final auth = ref.watch(authenticationProvider); final authenticated = ref.watch(metadataPluginAuthenticatedProvider);
final albumsQuery = ref.watch(favoriteAlbumsProvider); final albumsQuery = ref.watch(metadataPluginSavedAlbumsProvider);
final albumsQueryNotifier = ref.watch(favoriteAlbumsProvider.notifier); final albumsQueryNotifier =
ref.watch(metadataPluginSavedAlbumsProvider.notifier);
final controller = useScrollController(); final controller = useScrollController();
@ -39,7 +42,7 @@ class UserAlbumsPage extends HookConsumerWidget {
} }
return albumsQuery.asData?.value.items return albumsQuery.asData?.value.items
.map((e) => ( .map((e) => (
weightedRatio(e.name!, searchText.value), weightedRatio(e.name, searchText.value),
e, e,
)) ))
.sorted((a, b) => b.$1.compareTo(a.$1)) .sorted((a, b) => b.$1.compareTo(a.$1))
@ -49,7 +52,7 @@ class UserAlbumsPage extends HookConsumerWidget {
[]; [];
}, [albumsQuery.asData?.value, searchText.value]); }, [albumsQuery.asData?.value, searchText.value]);
if (auth.asData?.value == null) { if (authenticated.asData?.value != true) {
return const AnonymousFallback(); return const AnonymousFallback();
} }

View File

@ -5,18 +5,20 @@ import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image;
import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; 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/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/playbutton_view/playbutton_view.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/modules/playlist/playlist_create_dialog.dart';
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/modules/playlist/playlist_card.dart';
import 'package:spotube/extensions/context.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/spotify/spotify.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'; import 'package:auto_route/auto_route.dart';
@RoutePage() @RoutePage()
@ -28,42 +30,47 @@ class UserPlaylistsPage extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final searchText = useState(''); 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 = final playlistsQueryNotifier =
ref.watch(favoritePlaylistsProvider.notifier); ref.watch(metadataPluginSavedPlaylistsProvider.notifier);
final likedTracksPlaylist = useMemoized( final likedTracksPlaylist = useMemoized(
() => PlaylistSimple() () => me.asData?.value == null
..name = context.l10n.liked_tracks ? null
..description = context.l10n.liked_tracks_description : SpotubeSimplePlaylistObject(
..type = "playlist" id: "liked-tracks",
..collaborative = false name: context.l10n.liked_tracks,
..public = false description: context.l10n.liked_tracks_description,
..id = "user-liked-tracks" externalUri: "",
..images = [ owner: me.asData!.value!,
Image() images: [
..height = 300 SpotubeImageObject(
..width = 300 url: Assets.likedTracks.path,
..url = "assets/liked-tracks.jpg" width: 300,
], height: 300,
[context.l10n], )
]),
[context.l10n, me.asData?.value],
); );
final playlists = useMemoized( final playlists = useMemoized(
() { () {
if (searchText.value.isEmpty) { if (searchText.value.isEmpty) {
return [ return [
likedTracksPlaylist, if (likedTracksPlaylist != null) likedTracksPlaylist,
...?playlistsQuery.asData?.value.items, ...?playlistsQuery.asData?.value.items
as List<SpotubeSimplePlaylistObject>?,
]; ];
} }
return [ return [
likedTracksPlaylist, if (likedTracksPlaylist != null) likedTracksPlaylist,
...?playlistsQuery.asData?.value.items, ...?playlistsQuery.asData?.value.items
as List<SpotubeSimplePlaylistObject>?,
] ]
.map((e) => (weightedRatio(e.name!, searchText.value), e)) .map((e) => (weightedRatio(e.name, searchText.value), e))
.sorted((a, b) => b.$1.compareTo(a.$1)) .sorted((a, b) => b.$1.compareTo(a.$1))
.where((e) => e.$1 > 50) .where((e) => e.$1 > 50)
.map((e) => e.$2) .map((e) => e.$2)
@ -74,13 +81,13 @@ class UserPlaylistsPage extends HookConsumerWidget {
final controller = useScrollController(); final controller = useScrollController();
if (auth.asData?.value == null) { if (authenticated.asData?.value != true) {
return const AnonymousFallback(); return const AnonymousFallback();
} }
return material.RefreshIndicator.adaptive( return material.RefreshIndicator.adaptive(
onRefresh: () async { onRefresh: () async {
ref.invalidate(favoritePlaylistsProvider); ref.invalidate(metadataPluginSavedPlaylistsProvider);
}, },
child: SafeArea( child: SafeArea(
bottom: false, bottom: false,

View File

@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/track_presentation/presentation_props.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/track_presentation.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/pages/playlist/playlist.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@ -12,7 +13,7 @@ import 'package:auto_route/auto_route.dart';
class LikedPlaylistPage extends HookConsumerWidget { class LikedPlaylistPage extends HookConsumerWidget {
static const name = PlaylistPage.name; static const name = PlaylistPage.name;
final PlaylistSimple playlist; final SpotubeSimplePlaylistObject playlist;
const LikedPlaylistPage({ const LikedPlaylistPage({
super.key, super.key,
required this.playlist, required this.playlist,
@ -42,14 +43,14 @@ class LikedPlaylistPage extends HookConsumerWidget {
ref.invalidate(likedTracksProvider); ref.invalidate(likedTracksProvider);
}, },
), ),
title: playlist.name!, title: playlist.name,
description: playlist.description, description: playlist.description,
tracks: tracks, tracks: tracks,
routePath: '/playlist/${playlist.id}', routePath: '/playlist/${playlist.id}',
isLiked: false, isLiked: false,
shareUrl: null, shareUrl: null,
onHeart: null, onHeart: null,
owner: playlist.owner?.displayName, owner: playlist.owner.name,
), ),
), ),
); );

View File

@ -2,13 +2,13 @@ import 'package:flutter/material.dart' as material;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart' hide Page; import 'package:flutter/material.dart' hide Page;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/dialogs/prompt_dialog.dart'; import 'package:spotube/components/dialogs/prompt_dialog.dart';
import 'package:spotube/components/track_presentation/presentation_props.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/track_presentation.dart';
import 'package:spotube/components/track_presentation/use_is_user_playlist.dart'; import 'package:spotube/components/track_presentation/use_is_user_playlist.dart';
import 'package:spotube/extensions/context.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:spotube/provider/spotify/spotify.dart';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@ -16,22 +16,22 @@ import 'package:auto_route/auto_route.dart';
class PlaylistPage extends HookConsumerWidget { class PlaylistPage extends HookConsumerWidget {
static const name = "playlist"; static const name = "playlist";
final PlaylistSimple _playlist; final SpotubeSimplePlaylistObject _playlist;
final String id; final String id;
const PlaylistPage({ const PlaylistPage({
super.key, super.key,
@PathParam("id") required this.id, @PathParam("id") required this.id,
required PlaylistSimple playlist, required SpotubeSimplePlaylistObject playlist,
}) : _playlist = playlist; }) : _playlist = playlist;
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final playlist = ref final playlist = ref
.watch( .watch(
favoritePlaylistsProvider.select( metadataPluginSavedPlaylistsProvider.select(
(value) => value.whenData( (value) => value.whenData(
(value) => (value) => (value.items as List<SpotubeSimplePlaylistObject>)
value.items.firstWhereOrNull((s) => s.id == _playlist.id), .firstWhereOrNull((s) => s.id == _playlist.id),
), ),
), ),
) )
@ -39,21 +39,21 @@ class PlaylistPage extends HookConsumerWidget {
?.value ?? ?.value ??
_playlist; _playlist;
final tracks = ref.watch(playlistTracksProvider(playlist.id!)); final tracks = ref.watch(playlistTracksProvider(playlist.id));
final tracksNotifier = final tracksNotifier =
ref.watch(playlistTracksProvider(playlist.id!).notifier); ref.watch(playlistTracksProvider(playlist.id).notifier);
final isFavoritePlaylist = final isFavoritePlaylist =
ref.watch(isFavoritePlaylistProvider(playlist.id!)); ref.watch(isFavoritePlaylistProvider(playlist.id));
final favoritePlaylistsNotifier = 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( return material.RefreshIndicator.adaptive(
onRefresh: () async { onRefresh: () async {
ref.invalidate(playlistTracksProvider(playlist.id!)); ref.invalidate(playlistTracksProvider(playlist.id));
ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); ref.invalidate(isFavoritePlaylistProvider(playlist.id));
ref.invalidate(favoritePlaylistsProvider); ref.invalidate(favoritePlaylistsProvider);
}, },
child: TrackPresentation( child: TrackPresentation(
@ -67,21 +67,20 @@ class PlaylistPage extends HookConsumerWidget {
isLoading: tracks.isLoading || tracks.isLoadingNextPage, isLoading: tracks.isLoading || tracks.isLoadingNextPage,
onFetchMore: tracksNotifier.fetchMore, onFetchMore: tracksNotifier.fetchMore,
onRefresh: () async { onRefresh: () async {
ref.invalidate(playlistTracksProvider(playlist.id!)); ref.invalidate(playlistTracksProvider(playlist.id));
}, },
onFetchAll: () async { onFetchAll: () async {
return await tracksNotifier.fetchAll(); return await tracksNotifier.fetchAll();
}, },
), ),
title: playlist.name!, title: playlist.name,
description: playlist.description, description: playlist.description,
owner: playlist.owner?.displayName, owner: playlist.owner.name,
ownerImage: playlist.owner?.images?.lastOrNull?.url, ownerImage: playlist.owner.images.lastOrNull?.url,
tracks: tracks.asData?.value.items ?? [], tracks: tracks.asData?.value.items ?? [],
routePath: '/playlist/${playlist.id}', routePath: '/playlist/${playlist.id}',
isLiked: isFavoritePlaylist.asData?.value ?? false, isLiked: isFavoritePlaylist.asData?.value ?? false,
shareUrl: playlist.externalUrls?.spotify ?? shareUrl: playlist.externalUri,
"https://open.spotify.com/playlist/${playlist.id}",
onHeart: isFavoritePlaylist.asData?.value == null onHeart: isFavoritePlaylist.asData?.value == null
? null ? null
: () async { : () async {

View File

@ -142,16 +142,16 @@ class TrackPage extends HookConsumerWidget {
children: [ children: [
const Icon(SpotubeIcons.album), const Icon(SpotubeIcons.album),
const Gap(5), const Gap(5),
Flexible( // Flexible(
child: LinkText( // child: LinkText(
track.album!.name!, // track.album!.name!,
AlbumRoute( // AlbumRoute(
id: track.album!.id!, // id: track.album!.id!,
album: track.album!, // album: track.album!,
), // ),
push: true, // push: true,
), // ),
), // ),
], ],
), ),
const Gap(10), const Gap(10),

View File

@ -26,8 +26,8 @@ class MetadataPluginSavedAlbumNotifier
await update((state) async { await update((state) async {
(await metadataPlugin).album.save(albums.map((e) => e.id).toList()); (await metadataPlugin).album.save(albums.map((e) => e.id).toList());
return state.copyWith( return state.copyWith(
items: [...state.items, albums], items: [...state.items, ...albums],
) as SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>; );
}); });
for (final album in albums) { for (final album in albums) {
@ -42,12 +42,10 @@ class MetadataPluginSavedAlbumNotifier
return state.copyWith( return state.copyWith(
items: state.items items: state.items
.where( .where(
(e) => (e) => albumIds.contains((e).id) == false,
albumIds.contains((e as SpotubeSimpleAlbumObject).id) ==
false,
) )
.toList() as List<SpotubeSimpleAlbumObject>, .toList(),
) as SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>; );
}); });
for (final album in albums) { for (final album in albums) {

View File

@ -26,8 +26,8 @@ class MetadataPluginSavedArtistNotifier
await update((state) async { await update((state) async {
(await metadataPlugin).artist.save(artists.map((e) => e.id).toList()); (await metadataPlugin).artist.save(artists.map((e) => e.id).toList());
return state.copyWith( return state.copyWith(
items: [...state.items, artists], items: [...state.items, ...artists],
) as SpotubePaginationResponseObject<SpotubeFullArtistObject>; );
}); });
for (final artist in artists) { for (final artist in artists) {
@ -42,12 +42,10 @@ class MetadataPluginSavedArtistNotifier
return state.copyWith( return state.copyWith(
items: state.items items: state.items
.where( .where(
(e) => (e) => artistIds.contains((e).id) == false,
artistIds.contains((e as SpotubeFullArtistObject).id) ==
false,
) )
.toList() as List<SpotubeFullArtistObject>, .toList(),
) as SpotubePaginationResponseObject<SpotubeFullArtistObject>; );
}); });
for (final artist in artists) { for (final artist in artists) {

View File

@ -36,8 +36,8 @@ class MetadataPluginSavedPlaylistsNotifier
state.value!.copyWith( state.value!.copyWith(
items: state.value!.items items: state.value!.items
.map((element) => element.id == playlist.id ? playlist : element) .map((element) => element.id == playlist.id ? playlist : element)
.toList() as List<SpotubeSimplePlaylistObject>, .toList(),
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>, ),
); );
} }
@ -46,7 +46,7 @@ class MetadataPluginSavedPlaylistsNotifier
(await metadataPlugin).playlist.save(playlist.id); (await metadataPlugin).playlist.save(playlist.id);
return state.copyWith( return state.copyWith(
items: [...state.items, playlist], items: [...state.items, playlist],
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>; );
}); });
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
@ -56,10 +56,8 @@ class MetadataPluginSavedPlaylistsNotifier
await update((state) async { await update((state) async {
(await metadataPlugin).playlist.unsave(playlist.id); (await metadataPlugin).playlist.unsave(playlist.id);
return state.copyWith( return state.copyWith(
items: state.items items: state.items.where((e) => (e).id != playlist.id).toList(),
.where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) );
.toList() as List<SpotubeSimplePlaylistObject>,
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>;
}); });
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
@ -69,10 +67,8 @@ class MetadataPluginSavedPlaylistsNotifier
await update((state) async { await update((state) async {
(await metadataPlugin).playlist.deletePlaylist(playlist.id); (await metadataPlugin).playlist.deletePlaylist(playlist.id);
return state.copyWith( return state.copyWith(
items: state.items items: state.items.where((e) => (e).id != playlist.id).toList(),
.where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) );
.toList() as List<SpotubeSimplePlaylistObject>,
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>;
}); });
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));

View File

@ -2,8 +2,6 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/metadata/metadata.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'; import 'package:spotube/provider/metadata_plugin/utils/common.dart';
abstract class FamilyPaginatedAsyncNotifier<K, A> abstract class FamilyPaginatedAsyncNotifier<K, A>
@ -22,17 +20,20 @@ abstract class FamilyPaginatedAsyncNotifier<K, A>
state.value!.nextOffset!, state.value!.nextOffset!,
state.value!.limit, state.value!.limit,
); );
return newState.copyWith(items: [
...state.value!.items as List<K>, final oldItems =
...newState.items as List<K>, state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
]) as SpotubePaginationResponseObject<K>; final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
return newState.copyWith(items: <K>[...oldItems, ...items])
as SpotubePaginationResponseObject<K>;
}, },
); );
} }
Future<List<K>> fetchAll() async { Future<List<K>> fetchAll() async {
if (state.value == null) return []; if (state.value == null) return [];
if (!state.value!.hasMore) return state.value!.items as List<K>; if (!state.value!.hasMore) return state.value!.items.cast<K>();
bool hasMore = true; bool hasMore = true;
while (hasMore) { while (hasMore) {
@ -43,14 +44,14 @@ abstract class FamilyPaginatedAsyncNotifier<K, A>
); );
hasMore = newState.hasMore; hasMore = newState.hasMore;
return newState.copyWith(items: [ final oldItems = state.items.isEmpty ? <K>[] : state.items.cast<K>();
...state.items as List<K>, final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
...newState.items as List<K>, return newState.copyWith(items: <K>[...oldItems, ...items])
]) as SpotubePaginationResponseObject<K>; as SpotubePaginationResponseObject<K>;
}); });
} }
return state.value!.items as List<K>; return state.value!.items.cast<K>();
} }
} }
@ -71,8 +72,8 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
state.value!.limit, state.value!.limit,
); );
return newState.copyWith(items: [ return newState.copyWith(items: [
...state.value!.items as List<K>, ...state.value!.items.cast<K>(),
...newState.items as List<K>, ...newState.items.cast<K>(),
]) as SpotubePaginationResponseObject<K>; ]) as SpotubePaginationResponseObject<K>;
}, },
); );
@ -80,7 +81,7 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
Future<List<K>> fetchAll() async { Future<List<K>> fetchAll() async {
if (state.value == null) return []; if (state.value == null) return [];
if (!state.value!.hasMore) return state.value!.items as List<K>; if (!state.value!.hasMore) return state.value!.items.cast<K>();
bool hasMore = true; bool hasMore = true;
while (hasMore) { while (hasMore) {
@ -92,12 +93,12 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
hasMore = newState.hasMore; hasMore = newState.hasMore;
return newState.copyWith(items: [ return newState.copyWith(items: [
...state.items as List<K>, ...state.items.cast<K>(),
...newState.items as List<K>, ...newState.items.cast<K>(),
]) as SpotubePaginationResponseObject<K>; ]) as SpotubePaginationResponseObject<K>;
}); });
} }
return state.value!.items as List<K>; return state.value!.items.cast<K>();
} }
} }

View File

@ -22,17 +22,20 @@ mixin PaginatedAsyncNotifierMixin<K>
state.value!.nextOffset!, state.value!.nextOffset!,
state.value!.limit, state.value!.limit,
); );
return newState.copyWith(items: [
...state.value!.items as List<K>, final oldItems =
...newState.items as List<K>, state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
]) as SpotubePaginationResponseObject<K>; final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
return newState.copyWith(items: <K>[...oldItems, ...items])
as SpotubePaginationResponseObject<K>;
}, },
); );
} }
Future<List<K>> fetchAll() async { Future<List<K>> fetchAll() async {
if (state.value == null) return []; if (state.value == null) return [];
if (!state.value!.hasMore) return state.value!.items as List<K>; if (!state.value!.hasMore) return state.value!.items.cast<K>();
bool hasMore = true; bool hasMore = true;
while (hasMore) { while (hasMore) {
@ -43,14 +46,14 @@ mixin PaginatedAsyncNotifierMixin<K>
); );
hasMore = newState.hasMore; hasMore = newState.hasMore;
return newState.copyWith(items: [ final oldItems = state.items.isEmpty ? <K>[] : state.items.cast<K>();
...state.items as List<K>, final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
...newState.items as List<K>, return newState.copyWith(items: <K>[...oldItems, ...items])
]) as SpotubePaginationResponseObject<K>; as SpotubePaginationResponseObject<K>;
}); });
} }
return state.value!.items as List<K>; return state.value!.items.cast<K>();
} }
} }

View File

@ -33,7 +33,7 @@ class MetadataPluginAlbumEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeFullTrackObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeFullTrackObject.fromJson(json.cast<String, dynamic>()), SpotubeFullTrackObject.fromJson(json.cast<String, dynamic>()),
@ -52,7 +52,7 @@ class MetadataPluginAlbumEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeSimpleAlbumObject.fromJson(json.cast<String, dynamic>()), SpotubeSimpleAlbumObject.fromJson(json.cast<String, dynamic>()),

View File

@ -33,7 +33,7 @@ class MetadataPluginArtistEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeFullTrackObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => SpotubeFullTrackObject.fromJson( (Map json) => SpotubeFullTrackObject.fromJson(
json.cast<String, dynamic>(), json.cast<String, dynamic>(),
@ -55,7 +55,7 @@ class MetadataPluginArtistEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => SpotubeSimpleAlbumObject.fromJson( (Map json) => SpotubeSimpleAlbumObject.fromJson(
json.cast<String, dynamic>(), json.cast<String, dynamic>(),

View File

@ -33,7 +33,7 @@ class MetadataPluginPlaylistEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeFullTrackObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeFullTrackObject.fromJson(json.cast<String, dynamic>()), SpotubeFullTrackObject.fromJson(json.cast<String, dynamic>()),

View File

@ -30,7 +30,7 @@ class MetadataPluginUserEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeFullTrackObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeFullTrackObject.fromJson(json.cast<String, dynamic>()), SpotubeFullTrackObject.fromJson(json.cast<String, dynamic>()),
@ -50,7 +50,8 @@ class MetadataPluginUserEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<
SpotubeSimplePlaylistObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeSimplePlaylistObject.fromJson(json.cast<String, dynamic>()), SpotubeSimplePlaylistObject.fromJson(json.cast<String, dynamic>()),
@ -70,7 +71,7 @@ class MetadataPluginUserEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeSimpleAlbumObject.fromJson(json.cast<String, dynamic>()), SpotubeSimpleAlbumObject.fromJson(json.cast<String, dynamic>()),
@ -90,7 +91,7 @@ class MetadataPluginUserEndpoint {
}..removeWhere((key, value) => value == null), }..removeWhere((key, value) => value == null),
) as Map; ) as Map;
return SpotubePaginationResponseObject.fromJson( return SpotubePaginationResponseObject<SpotubeFullArtistObject>.fromJson(
raw.cast<String, dynamic>(), raw.cast<String, dynamic>(),
(Map json) => (Map json) =>
SpotubeFullArtistObject.fromJson(json.cast<String, dynamic>()), SpotubeFullArtistObject.fromJson(json.cast<String, dynamic>()),

View File

@ -112,7 +112,6 @@ dependencies:
sliding_up_panel: ^2.0.0+1 sliding_up_panel: ^2.0.0+1
sliver_tools: ^0.2.12 sliver_tools: ^0.2.12
smtc_windows: ^1.0.0 smtc_windows: ^1.0.0
spotify: ^0.13.7
sqlite3: ^2.4.3 sqlite3: ^2.4.3
sqlite3_flutter_libs: ^0.5.23 sqlite3_flutter_libs: ^0.5.23
stroke_text: ^0.0.2 stroke_text: ^0.0.2
@ -157,6 +156,7 @@ dependencies:
url: https://github.com/KRTirtho/hetu_spotube_plugin.git url: https://github.com/KRTirtho/hetu_spotube_plugin.git
ref: main ref: main
get_it: ^8.0.3 get_it: ^8.0.3
spotify: ^0.13.7
dev_dependencies: dev_dependencies:
build_runner: ^2.4.13 build_runner: ^2.4.13