Compare commits

..

2 Commits

Author SHA1 Message Date
Kingkor Roy Tirtho
abe04b28b2 feat(metadata-plugin): add local storage api 2025-05-02 13:52:07 +06:00
Kingkor Roy Tirtho
8ac30c0031 feat(metadata-plugin): add pagination support, feed and playlist CRUD endpoints 2025-05-02 11:36:09 +06:00
9 changed files with 1387 additions and 198 deletions

View File

@ -0,0 +1,14 @@
part of 'metadata.dart';
@freezed
class SpotubeFeedObject with _$SpotubeFeedObject {
factory SpotubeFeedObject({
required final String uid,
required final String name,
required final String externalUrl,
@Default([]) final List<SpotubeImageObject> images,
}) = _SpotubeFeedObject;
factory SpotubeFeedObject.fromJson(Map<String, dynamic> json) =>
_$SpotubeFeedObjectFromJson(json);
}

View File

@ -7,8 +7,12 @@ part 'metadata.freezed.dart';
part 'album.dart'; part 'album.dart';
part 'artist.dart'; part 'artist.dart';
part 'feed.dart';
part 'image.dart'; part 'image.dart';
part 'pagination.dart';
part 'playlist.dart'; part 'playlist.dart';
part 'search.dart'; part 'search.dart';
part 'track.dart'; part 'track.dart';
part 'user.dart'; part 'user.dart';
part 'plugin.dart';

File diff suppressed because it is too large Load Diff

View File

@ -60,6 +60,27 @@ Map<String, dynamic> _$$SpotubeArtistObjectImplToJson(
'externalUrl': instance.externalUrl, 'externalUrl': instance.externalUrl,
}; };
_$SpotubeFeedObjectImpl _$$SpotubeFeedObjectImplFromJson(Map json) =>
_$SpotubeFeedObjectImpl(
uid: json['uid'] as String,
name: json['name'] as String,
externalUrl: json['externalUrl'] as String,
images: (json['images'] as List<dynamic>?)
?.map((e) => SpotubeImageObject.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList() ??
const [],
);
Map<String, dynamic> _$$SpotubeFeedObjectImplToJson(
_$SpotubeFeedObjectImpl instance) =>
<String, dynamic>{
'uid': instance.uid,
'name': instance.name,
'externalUrl': instance.externalUrl,
'images': instance.images.map((e) => e.toJson()).toList(),
};
_$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => _$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) =>
_$SpotubeImageObjectImpl( _$SpotubeImageObjectImpl(
url: json['url'] as String, url: json['url'] as String,
@ -75,6 +96,31 @@ Map<String, dynamic> _$$SpotubeImageObjectImplToJson(
'height': instance.height, 'height': instance.height,
}; };
_$SpotubePaginationResponseObjectImpl<T>
_$$SpotubePaginationResponseObjectImplFromJson<T>(
Map json,
T Function(Object? json) fromJsonT,
) =>
_$SpotubePaginationResponseObjectImpl<T>(
total: (json['total'] as num).toInt(),
nextCursor: json['nextCursor'] as String?,
limit: json['limit'] as String,
hasMore: json['hasMore'] as bool,
items: (json['items'] as List<dynamic>).map(fromJsonT).toList(),
);
Map<String, dynamic> _$$SpotubePaginationResponseObjectImplToJson<T>(
_$SpotubePaginationResponseObjectImpl<T> instance,
Object? Function(T value) toJsonT,
) =>
<String, dynamic>{
'total': instance.total,
'nextCursor': instance.nextCursor,
'limit': instance.limit,
'hasMore': instance.hasMore,
'items': instance.items.map(toJsonT).toList(),
};
_$SpotubePlaylistObjectImpl _$$SpotubePlaylistObjectImplFromJson(Map json) => _$SpotubePlaylistObjectImpl _$$SpotubePlaylistObjectImplFromJson(Map json) =>
_$SpotubePlaylistObjectImpl( _$SpotubePlaylistObjectImpl(
uid: json['uid'] as String, uid: json['uid'] as String,
@ -110,35 +156,21 @@ Map<String, dynamic> _$$SpotubePlaylistObjectImplToJson(
_$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson( _$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson(
Map json) => Map json) =>
_$SpotubeSearchResponseObjectImpl( _$SpotubeSearchResponseObjectImpl(
tracks: (json['tracks'] as List<dynamic>?) tracks: _paginationTracksFromJson(json['tracks'] as Map<String, dynamic>),
?.map((e) => SpotubeTrackObject.fromJson( albums: _paginationAlbumsFromJson(json['albums'] as Map<String, dynamic>),
Map<String, dynamic>.from(e as Map))) artists:
.toList() ?? _paginationArtistsFromJson(json['artists'] as Map<String, dynamic>),
const [], playlists: _paginationPlaylistsFromJson(
albums: (json['albums'] as List<dynamic>?) json['playlists'] as Map<String, dynamic>),
?.map((e) => SpotubeAlbumObject.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList() ??
const [],
artists: (json['artists'] as List<dynamic>?)
?.map((e) => SpotubeArtistObject.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList() ??
const [],
playlists: (json['playlists'] as List<dynamic>?)
?.map((e) => SpotubePlaylistObject.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList() ??
const [],
); );
Map<String, dynamic> _$$SpotubeSearchResponseObjectImplToJson( Map<String, dynamic> _$$SpotubeSearchResponseObjectImplToJson(
_$SpotubeSearchResponseObjectImpl instance) => _$SpotubeSearchResponseObjectImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'tracks': instance.tracks.map((e) => e.toJson()).toList(), 'tracks': _paginationToJson(instance.tracks),
'albums': instance.albums.map((e) => e.toJson()).toList(), 'albums': _paginationToJson(instance.albums),
'artists': instance.artists.map((e) => e.toJson()).toList(), 'artists': _paginationToJson(instance.artists),
'playlists': instance.playlists.map((e) => e.toJson()).toList(), 'playlists': _paginationToJson(instance.playlists),
}; };
_$SpotubeTrackObjectImpl _$$SpotubeTrackObjectImplFromJson(Map json) => _$SpotubeTrackObjectImpl _$$SpotubeTrackObjectImplFromJson(Map json) =>
@ -191,3 +223,26 @@ Map<String, dynamic> _$$SpotubeUserObjectImplToJson(
'externalUrl': instance.externalUrl, 'externalUrl': instance.externalUrl,
'displayName': instance.displayName, 'displayName': instance.displayName,
}; };
_$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) =>
_$PluginConfigurationImpl(
type: $enumDecode(_$PluginTypeEnumMap, json['type']),
name: json['name'] as String,
description: json['description'] as String,
version: json['version'] as String,
author: json['author'] as String,
);
Map<String, dynamic> _$$PluginConfigurationImplToJson(
_$PluginConfigurationImpl instance) =>
<String, dynamic>{
'type': _$PluginTypeEnumMap[instance.type]!,
'name': instance.name,
'description': instance.description,
'version': instance.version,
'author': instance.author,
};
const _$PluginTypeEnumMap = {
PluginType.metadata: 'metadata',
};

View File

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

View File

@ -0,0 +1,21 @@
part of 'metadata.dart';
enum PluginType { metadata }
@freezed
class PluginConfiguration with _$PluginConfiguration {
const PluginConfiguration._();
factory PluginConfiguration({
required PluginType type,
required String name,
required String description,
required String version,
required String author,
}) = _PluginConfiguration;
factory PluginConfiguration.fromJson(Map<String, dynamic> json) =>
_$PluginConfigurationFromJson(json);
String get slug => name.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '-');
}

View File

@ -1,12 +1,71 @@
part of 'metadata.dart'; part of 'metadata.dart';
SpotubePaginationResponseObject<SpotubeTrackObject> _paginationTracksFromJson(
Map<String, dynamic> json,
) {
return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
json,
(json) => SpotubeTrackObject.fromJson(json),
);
}
SpotubePaginationResponseObject<SpotubeAlbumObject> _paginationAlbumsFromJson(
Map<String, dynamic> json,
) {
return SpotubePaginationResponseObject<SpotubeAlbumObject>.fromJson(
json,
(json) => SpotubeAlbumObject.fromJson(json),
);
}
SpotubePaginationResponseObject<SpotubeArtistObject> _paginationArtistsFromJson(
Map<String, dynamic> json,
) {
return SpotubePaginationResponseObject<SpotubeArtistObject>.fromJson(
json,
(json) => SpotubeArtistObject.fromJson(json),
);
}
SpotubePaginationResponseObject<SpotubePlaylistObject>
_paginationPlaylistsFromJson(
Map<String, dynamic> json,
) {
return SpotubePaginationResponseObject<SpotubePlaylistObject>.fromJson(
json,
(json) => SpotubePlaylistObject.fromJson(json),
);
}
Map<String, dynamic>? _paginationToJson(
SpotubePaginationResponseObject? instance,
) {
return instance?.toJson((item) => item.toJson());
}
@freezed @freezed
class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject { class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject {
factory SpotubeSearchResponseObject({ factory SpotubeSearchResponseObject({
@Default([]) final List<SpotubeTrackObject> tracks, @JsonKey(
@Default([]) final List<SpotubeAlbumObject> albums, fromJson: _paginationTracksFromJson,
@Default([]) final List<SpotubeArtistObject> artists, toJson: _paginationToJson,
@Default([]) final List<SpotubePlaylistObject> playlists, )
final SpotubePaginationResponseObject<SpotubeTrackObject>? tracks,
@JsonKey(
fromJson: _paginationAlbumsFromJson,
toJson: _paginationToJson,
)
final SpotubePaginationResponseObject<SpotubeAlbumObject>? albums,
@JsonKey(
fromJson: _paginationArtistsFromJson,
toJson: _paginationToJson,
)
final SpotubePaginationResponseObject<SpotubeArtistObject>? artists,
@JsonKey(
fromJson: _paginationPlaylistsFromJson,
toJson: _paginationToJson,
)
final SpotubePaginationResponseObject<SpotubePlaylistObject>? playlists,
}) = _SpotubeSearchResponseObject; }) = _SpotubeSearchResponseObject;
factory SpotubeSearchResponseObject.fromJson(Map<String, dynamic> json) => factory SpotubeSearchResponseObject.fromJson(Map<String, dynamic> json) =>

View File

@ -0,0 +1,60 @@
import 'package:flutter_js/flutter_js.dart';
import 'package:shared_preferences/shared_preferences.dart';
class PluginLocalStorageApi {
final JavascriptRuntime runtime;
final SharedPreferences sharedPreferences;
final String pluginName;
PluginLocalStorageApi({
required this.runtime,
required this.sharedPreferences,
required this.pluginName,
}) {
runtime.onMessage("LocalStorage.getItem", (args) {
final key = args[0];
final value = getItem(key);
runtime.evaluate(
"""
eventEmitter.emit('LocalStorage.getItem', ${value != null ? "'$value'" : "null"});
""",
);
});
runtime.onMessage("LocalStorage.setItem", (args) {
final map = args[0] as Map<String, dynamic>;
setItem(map["key"], map["value"]);
});
runtime.onMessage("LocalStorage.removeItem", (args) {
final map = args[0];
removeItem(map["key"]);
});
runtime.onMessage("LocalStorage.clear", (args) {
clear();
});
}
void setItem(String key, String value) async {
await sharedPreferences.setString("plugin.$pluginName.$key", value);
}
String? getItem(String key) {
return sharedPreferences.getString("plugin.$pluginName.$key");
}
void removeItem(String key) async {
await sharedPreferences.remove("plugin.$pluginName.$key");
}
void clear() async {
final keys = sharedPreferences.getKeys();
for (String key in keys) {
if (key.startsWith("plugin.$pluginName.")) {
await sharedPreferences.remove(key);
}
}
}
}

View File

@ -4,20 +4,23 @@ import 'dart:convert';
import 'package:flutter_js/extensions/fetch.dart'; import 'package:flutter_js/extensions/fetch.dart';
import 'package:flutter_js/extensions/xhr.dart'; import 'package:flutter_js/extensions/xhr.dart';
import 'package:flutter_js/flutter_js.dart'; import 'package:flutter_js/flutter_js.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/metadata/apis/localstorage.dart';
const int defaultMetadataLimit = 20; const defaultMetadataLimit = "20";
const int defaultMetadataOffset = 0;
/// Signature for metadata and related methods that will return Spotube native /// Signature for metadata and related methods that will return Spotube native
/// objects e.g. SpotubeTrack, SpotubePlaylist, etc. /// objects e.g. SpotubeTrack, SpotubePlaylist, etc.
class MetadataApiSignature { class MetadataApiSignature {
final JavascriptRuntime runtime; final JavascriptRuntime runtime;
final PluginLocalStorageApi localStorageApi;
MetadataApiSignature._(this.runtime); MetadataApiSignature._(this.runtime, this.localStorageApi);
static Future<MetadataApiSignature> init(String libraryCode) async { static Future<MetadataApiSignature> init(
String libraryCode, PluginConfiguration config) async {
final runtime = getJavascriptRuntime(xhr: true).enableXhr(); final runtime = getJavascriptRuntime(xhr: true).enableXhr();
runtime.enableHandlePromises(); runtime.enableHandlePromises();
await runtime.enableFetch(); await runtime.enableFetch();
@ -42,7 +45,17 @@ class MetadataApiSignature {
); );
} }
return MetadataApiSignature._(runtime); // Create all the PluginAPIs after library code is evaluated
final localStorageApi = PluginLocalStorageApi(
runtime: runtime,
sharedPreferences: await SharedPreferences.getInstance(),
pluginName: config.slug,
);
return MetadataApiSignature._(
runtime,
localStorageApi,
);
} }
void dispose() { void dispose() {
@ -97,73 +110,91 @@ class MetadataApiSignature {
return SpotubeTrackObject.fromJson(result); return SpotubeTrackObject.fromJson(result);
} }
Future<List<SpotubeTrackObject>> listTracks({ Future<SpotubePaginationResponseObject<SpotubeTrackObject>> listTracks({
List<String>? ids, List<String>? ids,
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final result = await invoke( final result = await invoke(
"metadataApi.listTracks", "metadataApi.listTracks",
[ [
ids, ids,
limit, limit,
offset, cursor,
], ],
); );
return result.map(SpotubeTrackObject.fromJson).toList(); return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
result,
SpotubeTrackObject.fromJson,
);
} }
Future<List<SpotubeTrackObject>> listTracksByAlbum( Future<SpotubePaginationResponseObject<SpotubeTrackObject>> listTracksByAlbum(
String albumId, { String albumId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listTracksByAlbum", "metadataApi.listTracksByAlbum",
[albumId, limit, offset], [albumId, limit, cursor],
); );
return res.map(SpotubeTrackObject.fromJson).toList(); return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
res,
SpotubeTrackObject.fromJson,
);
} }
Future<List<SpotubeTrackObject>> listTopTracksByArtist( Future<SpotubePaginationResponseObject<SpotubeTrackObject>>
listTopTracksByArtist(
String artistId, { String artistId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listTopTracksByArtist", "metadataApi.listTopTracksByArtist",
[artistId, limit, offset], [artistId, limit, cursor],
); );
return res.map(SpotubeTrackObject.fromJson).toList(); return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
res,
SpotubeTrackObject.fromJson,
);
} }
Future<List<SpotubeTrackObject>> listTracksByPlaylist( Future<SpotubePaginationResponseObject<SpotubeTrackObject>>
listTracksByPlaylist(
String playlistId, { String playlistId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listTracksByPlaylist", "metadataApi.listTracksByPlaylist",
[playlistId, limit, offset], [playlistId, limit, cursor],
); );
return res.map(SpotubeTrackObject.fromJson).toList(); return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
res,
SpotubeTrackObject.fromJson,
);
} }
Future<List<SpotubeTrackObject>> listUserSavedTracks( Future<SpotubePaginationResponseObject<SpotubeTrackObject>>
listUserSavedTracks(
String userId, { String userId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listUserSavedTracks", "metadataApi.listUserSavedTracks",
[userId, limit, offset], [userId, limit, cursor],
); );
return res.map(SpotubeTrackObject.fromJson).toList(); return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
res,
SpotubeTrackObject.fromJson,
);
} }
// ----- Album ------ // ----- Album ------
@ -173,43 +204,54 @@ class MetadataApiSignature {
return SpotubeAlbumObject.fromJson(res); return SpotubeAlbumObject.fromJson(res);
} }
Future<List<SpotubeAlbumObject>> listAlbums({ Future<SpotubePaginationResponseObject<SpotubeAlbumObject>> listAlbums({
List<String>? ids, List<String>? ids,
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listAlbums", "metadataApi.listAlbums",
[ids, limit, offset], [ids, limit, cursor],
); );
return res.map(SpotubeAlbumObject.fromJson).toList(); return SpotubePaginationResponseObject<SpotubeAlbumObject>.fromJson(
res,
SpotubeAlbumObject.fromJson,
);
} }
Future<List<SpotubeAlbumObject>> listAlbumsByArtist( Future<SpotubePaginationResponseObject<SpotubeAlbumObject>>
listAlbumsByArtist(
String artistId, { String artistId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listAlbumsByArtist", "metadataApi.listAlbumsByArtist",
[artistId, limit, offset], [artistId, limit, cursor],
); );
return res.map(SpotubeAlbumObject.fromJson).toList(); return SpotubePaginationResponseObject.fromJson(
res,
SpotubeAlbumObject.fromJson,
);
} }
Future<List<SpotubeAlbumObject>> listUserSavedAlbums( Future<SpotubePaginationResponseObject<SpotubeAlbumObject>>
listUserSavedAlbums(
String userId, { String userId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listUserSavedAlbums", "metadataApi.listUserSavedAlbums",
[userId, limit, offset], [userId, limit, cursor],
); );
return res.map(SpotubeAlbumObject.fromJson).toList(); return SpotubePaginationResponseObject.fromJson(
res,
SpotubeAlbumObject.fromJson,
);
} }
// ----- Playlist ------ // ----- Playlist ------
@ -219,30 +261,114 @@ class MetadataApiSignature {
return SpotubePlaylistObject.fromJson(res); return SpotubePlaylistObject.fromJson(res);
} }
Future<List<SpotubePlaylistObject>> listPlaylists({ Future<SpotubePaginationResponseObject<SpotubePlaylistObject>>
List<String>? ids, listFeedPlaylists(
int limit = defaultMetadataLimit, String feedId, {
int offset = defaultMetadataOffset, String limit = defaultMetadataLimit,
String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listPlaylists", "metadataApi.listFeedPlaylists",
[ids, limit, offset], [feedId, limit, cursor],
); );
return res.map(SpotubePlaylistObject.fromJson).toList(); return SpotubePaginationResponseObject.fromJson(
res,
SpotubePlaylistObject.fromJson,
);
} }
Future<List<SpotubePlaylistObject>> listUserSavedPlaylists( Future<SpotubePaginationResponseObject<SpotubePlaylistObject>>
listUserSavedPlaylists(
String userId, { String userId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listUserSavedPlaylists", "metadataApi.listUserSavedPlaylists",
[userId, limit, offset], [userId, limit, cursor],
); );
return res.map(SpotubePlaylistObject.fromJson).toList(); return SpotubePaginationResponseObject.fromJson(
res,
SpotubePlaylistObject.fromJson,
);
}
Future<SpotubePlaylistObject> createPlaylist(
String userId,
String name, {
String? description,
bool? public,
bool? collaborative,
String? imageBase64,
}) async {
final res = await invoke(
"metadataApi.createPlaylist",
[
userId,
name,
description,
public,
collaborative,
imageBase64,
],
);
return SpotubePlaylistObject.fromJson(res);
}
Future<void> updatePlaylist(
String playlistId, {
String? name,
String? description,
bool? public,
bool? collaborative,
String? imageBase64,
}) async {
await invoke(
"metadataApi.updatePlaylist",
[
playlistId,
name,
description,
public,
collaborative,
imageBase64,
],
);
}
Future<void> deletePlaylist(String userId, String playlistId) async {
await unsavePlaylist(userId, playlistId);
}
Future<void> addTracksToPlaylist(
String playlistId,
List<String> trackIds, {
int? position,
}) async {
await invoke(
"metadataApi.addTracksToPlaylist",
[
playlistId,
trackIds,
position,
],
);
}
Future<void> removeTracksFromPlaylist(
String playlistId,
List<String> trackIds,
) async {
await invoke(
"metadataApi.removeTracksFromPlaylist",
[
playlistId,
trackIds,
],
);
} }
// ----- Artist ------ // ----- Artist ------
@ -252,44 +378,70 @@ class MetadataApiSignature {
return SpotubeArtistObject.fromJson(res); return SpotubeArtistObject.fromJson(res);
} }
Future<List<SpotubeArtistObject>> listArtists({ Future<SpotubePaginationResponseObject<SpotubeArtistObject>> listArtists({
List<String>? ids, List<String>? ids,
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listArtists", "metadataApi.listArtists",
[ids, limit, offset], [ids, limit, cursor],
); );
return res.map(SpotubeArtistObject.fromJson).toList(); return SpotubePaginationResponseObject.fromJson(
res,
SpotubeArtistObject.fromJson,
);
} }
Future<List<SpotubeArtistObject>> listUserSavedArtists( Future<SpotubePaginationResponseObject<SpotubeArtistObject>>
listUserSavedArtists(
String userId, { String userId, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.listUserSavedArtists", "metadataApi.listUserSavedArtists",
[userId, limit, offset], [userId, limit, cursor],
); );
return res.map(SpotubeArtistObject.fromJson).toList(); return SpotubePaginationResponseObject.fromJson(
res,
SpotubeArtistObject.fromJson,
);
} }
// ----- Search ------ // ----- Search ------
Future<SpotubeSearchResponseObject> search( Future<SpotubeSearchResponseObject> search(
String query, { String query, {
int limit = defaultMetadataLimit, String limit = defaultMetadataLimit,
int offset = defaultMetadataOffset, String? cursor,
}) async { }) async {
final res = await invoke( final res = await invoke(
"metadataApi.search", "metadataApi.search",
[query, limit, offset], [query, limit, cursor],
); );
return res.map(SpotubeSearchResponseObject.fromJson).toList(); return SpotubeSearchResponseObject.fromJson(res);
}
// ----- Feed ------
Future<SpotubeFeedObject> getFeed(String id) async {
final res = await invoke("metadataApi.getFeed", [id]);
return SpotubeFeedObject.fromJson(res);
}
Future<SpotubePaginationResponseObject<SpotubeFeedObject>> listFeeds({
String limit = defaultMetadataLimit,
String? cursor,
}) async {
final res = await invoke("metadataApi.listFeeds", [limit, cursor]);
return SpotubePaginationResponseObject.fromJson(
res,
SpotubeFeedObject.fromJson,
);
} }
// ----- User ------ // ----- User ------