feat: add artist and album providers

This commit is contained in:
Kingkor Roy Tirtho 2025-06-14 22:43:20 +06:00
parent 326d8212f6
commit 758b0bc9d9
10 changed files with 335 additions and 5 deletions

View File

@ -0,0 +1,22 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
import 'package:spotube/services/metadata/endpoints/error.dart';
final metadataPluginAlbumProvider =
FutureProvider.autoDispose.family<SpotubeFullAlbumObject, String>(
(ref, id) async {
ref.cacheFor();
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
if (metadataPlugin == null) {
throw MetadataPluginException.noDefaultPlugin(
"No metadata plugin is not set",
);
}
return metadataPlugin.album.getAlbum(id);
},
);

View File

@ -0,0 +1,32 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart';
class MetadataPluginArtistAlbumNotifier
extends FamilyPaginatedAsyncNotifier<SpotubeSimpleAlbumObject, String> {
@override
Future<SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>> fetch(
int offset,
int limit,
) async {
return await (await metadataPlugin).artist.albums(
arg,
limit: limit,
offset: offset,
);
}
@override
build(arg) async {
ref.watch(metadataPluginProvider);
return await fetch(0, 20);
}
}
final metadataPluginArtistAlbumsProvider = AsyncNotifierFamilyProvider<
MetadataPluginArtistAlbumNotifier,
SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>,
String>(
() => MetadataPluginArtistAlbumNotifier(),
);

View File

@ -0,0 +1,22 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
import 'package:spotube/services/metadata/endpoints/error.dart';
final metadataPluginArtistProvider =
FutureProvider.autoDispose.family<SpotubeFullArtistObject, String>(
(ref, artistId) async {
ref.cacheFor();
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
if (metadataPlugin == null) {
throw MetadataPluginException.noDefaultPlugin(
"No metadata plugin is not set",
);
}
return metadataPlugin.artist.getArtist(artistId);
},
);

View File

@ -0,0 +1,38 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
class MetadataPluginArtistTopTracksNotifier
extends AutoDisposeFamilyPaginatedAsyncNotifier<SpotubeFullTrackObject,
String> {
MetadataPluginArtistTopTracksNotifier() : super();
@override
fetch(offset, limit) async {
final tracks = await (await metadataPlugin).artist.topTracks(
arg,
offset: offset,
limit: limit,
);
return tracks;
}
@override
build(arg) async {
ref.cacheFor();
ref.watch(metadataPluginProvider);
return await fetch(0, 20);
}
}
final metadataPluginArtistTopTracksProvider =
AutoDisposeAsyncNotifierProviderFamily<
MetadataPluginArtistTopTracksNotifier,
SpotubePaginationResponseObject<SpotubeFullTrackObject>,
String>(
() => MetadataPluginArtistTopTracksNotifier(),
);

View File

@ -0,0 +1,73 @@
import 'package:riverpod/riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
class MetadataPluginSavedArtistNotifier
extends PaginatedAsyncNotifier<SpotubeFullArtistObject> {
@override
Future<SpotubePaginationResponseObject<SpotubeFullArtistObject>> fetch(
int offset,
int limit,
) async {
return await (await metadataPlugin).user.savedArtists(
limit: limit,
offset: offset,
);
}
@override
build() async {
ref.watch(metadataPluginProvider);
return await fetch(0, 20);
}
Future<void> addFavorite(List<SpotubeFullArtistObject> artists) async {
await update((state) async {
(await metadataPlugin).artist.save(artists.map((e) => e.id).toList());
return state.copyWith(
items: [...state.items, artists],
) as SpotubePaginationResponseObject<SpotubeFullArtistObject>;
});
for (final artist in artists) {
ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id));
}
}
Future<void> removeFavorite(List<SpotubeFullArtistObject> artists) async {
await update((state) async {
final artistIds = artists.map((e) => e.id).toList();
(await metadataPlugin).artist.unsave(artistIds);
return state.copyWith(
items: state.items
.where(
(e) =>
artistIds.contains((e as SpotubeFullArtistObject).id) ==
false,
)
.toList() as List<SpotubeFullArtistObject>,
) as SpotubePaginationResponseObject<SpotubeFullArtistObject>;
});
for (final artist in artists) {
ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id));
}
}
}
final metadataPluginSavedArtistsProvider = AsyncNotifierProvider<
MetadataPluginSavedArtistNotifier,
SpotubePaginationResponseObject<SpotubeFullArtistObject>>(
() => MetadataPluginSavedArtistNotifier(),
);
final metadataPluginIsSavedArtistProvider =
FutureProvider.autoDispose.family<bool, String>(
(ref, artistId) async {
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
return metadataPlugin!.user
.isSavedArtists([artistId]).then((value) => value.first);
},
);

View File

@ -0,0 +1,22 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
import 'package:spotube/services/metadata/endpoints/error.dart';
final metadataPluginPlaylistProvider =
FutureProvider.autoDispose.family<SpotubeFullPlaylistObject, String>(
(ref, id) async {
ref.cacheFor();
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
if (metadataPlugin == null) {
throw MetadataPluginException.noDefaultPlugin(
"No metadata plugin is not set",
);
}
return metadataPlugin.playlist.getPlaylist(id);
},
);

View File

@ -0,0 +1,36 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
class MetadataPluginAlbumTracksNotifier
extends AutoDisposeFamilyPaginatedAsyncNotifier<SpotubeFullTrackObject,
String> {
MetadataPluginAlbumTracksNotifier() : super();
@override
fetch(offset, limit) async {
final tracks = await (await metadataPlugin).album.tracks(
arg,
offset: offset,
limit: limit,
);
return tracks;
}
@override
build(arg) async {
ref.cacheFor();
ref.watch(metadataPluginProvider);
return await fetch(0, 20);
}
}
final metadataPluginAlbumTracksProvider =
AutoDisposeAsyncNotifierProviderFamily<MetadataPluginAlbumTracksNotifier,
SpotubePaginationResponseObject<SpotubeFullTrackObject>, String>(
() => MetadataPluginAlbumTracksNotifier(),
);

View File

@ -4,9 +4,10 @@ import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart';
class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< class MetadataPluginPlaylistTracksNotifier
SpotubeFullTrackObject, String> { extends AutoDisposeFamilyPaginatedAsyncNotifier<SpotubeFullTrackObject,
PlaylistTracksNotifier() : super(); String> {
MetadataPluginPlaylistTracksNotifier() : super();
@override @override
fetch(offset, limit) async { fetch(offset, limit) async {
@ -29,7 +30,7 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
} }
final metadataPluginPlaylistTracksProvider = final metadataPluginPlaylistTracksProvider =
AutoDisposeAsyncNotifierProviderFamily<PlaylistTracksNotifier, AutoDisposeAsyncNotifierProviderFamily<MetadataPluginPlaylistTracksNotifier,
SpotubePaginationResponseObject<SpotubeFullTrackObject>, String>( SpotubePaginationResponseObject<SpotubeFullTrackObject>, String>(
() => PlaylistTracksNotifier(), () => MetadataPluginPlaylistTracksNotifier(),
); );

View File

@ -0,0 +1,79 @@
import 'package:hetu_script/hetu_script.dart';
import 'package:hetu_script/values.dart';
import 'package:spotube/models/metadata/metadata.dart';
class MetadataPluginArtistEndpoint {
final Hetu hetu;
MetadataPluginArtistEndpoint(this.hetu);
HTInstance get hetuMetadataArtist =>
(hetu.fetch("metadataPlugin") as HTInstance).memberGet("artist")
as HTInstance;
Future<SpotubeFullArtistObject> getArtist(String id) async {
final raw = await hetuMetadataArtist
.invoke("getArtist", positionalArgs: [id]) as Map;
return SpotubeFullArtistObject.fromJson(
raw.cast<String, dynamic>(),
);
}
Future<SpotubePaginationResponseObject<SpotubeFullTrackObject>> topTracks(
String id, {
int? offset,
int? limit,
}) async {
final raw = await hetuMetadataArtist.invoke(
"topTracks",
positionalArgs: [id],
namedArgs: {
"offset": offset,
"limit": limit,
}..removeWhere((key, value) => value == null),
) as Map;
return SpotubePaginationResponseObject.fromJson(
raw.cast<String, dynamic>(),
(Map json) => SpotubeFullTrackObject.fromJson(
json.cast<String, dynamic>(),
),
);
}
Future<SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>> albums(
String id, {
int? offset,
int? limit,
}) async {
final raw = await hetuMetadataArtist.invoke(
"albums",
positionalArgs: [id],
namedArgs: {
"offset": offset,
"limit": limit,
}..removeWhere((key, value) => value == null),
) as Map;
return SpotubePaginationResponseObject.fromJson(
raw.cast<String, dynamic>(),
(Map json) => SpotubeSimpleAlbumObject.fromJson(
json.cast<String, dynamic>(),
),
);
}
Future<void> save(List<String> ids) async {
await hetuMetadataArtist.invoke(
"save",
positionalArgs: [ids],
);
}
Future<void> unsave(List<String> ids) async {
await hetuMetadataArtist.invoke(
"unsave",
positionalArgs: [ids],
);
}
}

View File

@ -12,6 +12,7 @@ import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/services/metadata/apis/localstorage.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart';
import 'package:spotube/services/metadata/endpoints/album.dart'; import 'package:spotube/services/metadata/endpoints/album.dart';
import 'package:spotube/services/metadata/endpoints/artist.dart';
import 'package:spotube/services/metadata/endpoints/auth.dart'; import 'package:spotube/services/metadata/endpoints/auth.dart';
import 'package:spotube/services/metadata/endpoints/playlist.dart'; import 'package:spotube/services/metadata/endpoints/playlist.dart';
import 'package:spotube/services/metadata/endpoints/user.dart'; import 'package:spotube/services/metadata/endpoints/user.dart';
@ -74,12 +75,16 @@ class MetadataPlugin {
final Hetu hetu; final Hetu hetu;
late final MetadataAuthEndpoint auth; late final MetadataAuthEndpoint auth;
late final MetadataPluginAlbumEndpoint album; late final MetadataPluginAlbumEndpoint album;
late final MetadataPluginArtistEndpoint artist;
late final MetadataPluginPlaylistEndpoint playlist; late final MetadataPluginPlaylistEndpoint playlist;
late final MetadataPluginUserEndpoint user; late final MetadataPluginUserEndpoint user;
MetadataPlugin._(this.hetu) { MetadataPlugin._(this.hetu) {
auth = MetadataAuthEndpoint(hetu); auth = MetadataAuthEndpoint(hetu);
artist = MetadataPluginArtistEndpoint(hetu);
album = MetadataPluginAlbumEndpoint(hetu); album = MetadataPluginAlbumEndpoint(hetu);
playlist = MetadataPluginPlaylistEndpoint(hetu); playlist = MetadataPluginPlaylistEndpoint(hetu);
user = MetadataPluginUserEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu);