mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
feat: add playlist endpoint and providers
This commit is contained in:
parent
f8211cbcc7
commit
a9ba2582fb
@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotube/models/metadata/metadata.dart';
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||||
|
import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
|
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
|
||||||
import 'package:spotube/services/metadata/endpoints/error.dart';
|
import 'package:spotube/services/metadata/endpoints/error.dart';
|
||||||
|
|
||||||
@ -40,61 +41,63 @@ class FavoritePlaylistsNotifier
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> addFavorite(PlaylistSimple playlist) async {
|
Future<void> addFavorite(SpotubeSimplePlaylistObject playlist) async {
|
||||||
// await update((state) async {
|
await update((state) async {
|
||||||
// await spotify.invoke(
|
(await metadataPlugin)!.playlist.save(playlist.id);
|
||||||
// (api) => api.playlists.followPlaylist(playlist.id!),
|
return state.copyWith(
|
||||||
// );
|
items: [...state.items, playlist],
|
||||||
// return state.copyWith(
|
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>;
|
||||||
// items: [...state.items, playlist],
|
});
|
||||||
// );
|
|
||||||
// });
|
|
||||||
|
|
||||||
// ref.invalidate(isFavoritePlaylistProvider(playlist.id!));
|
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<void> removeFavorite(PlaylistSimple playlist) async {
|
Future<void> removeFavorite(SpotubeSimplePlaylistObject playlist) async {
|
||||||
// await update((state) async {
|
await update((state) async {
|
||||||
// await spotify.invoke(
|
(await metadataPlugin)!.playlist.unsave(playlist.id);
|
||||||
// (api) => api.playlists.unfollowPlaylist(playlist.id!),
|
return state.copyWith(
|
||||||
// );
|
items: state.items
|
||||||
// return state.copyWith(
|
.where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id)
|
||||||
// items: state.items.where((e) => e.id != playlist.id).toList(),
|
.toList() as List<SpotubeSimplePlaylistObject>,
|
||||||
// );
|
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>;
|
||||||
// });
|
});
|
||||||
|
|
||||||
// ref.invalidate(isFavoritePlaylistProvider(playlist.id!));
|
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<void> addTracks(String playlistId, List<String> trackIds) async {
|
Future<void> delete(SpotubeSimplePlaylistObject playlist) async {
|
||||||
// if (state.value == null) return;
|
await update((state) async {
|
||||||
|
(await metadataPlugin)!.playlist.deletePlaylist(playlist.id);
|
||||||
|
return state.copyWith(
|
||||||
|
items: state.items
|
||||||
|
.where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id)
|
||||||
|
.toList() as List<SpotubeSimplePlaylistObject>,
|
||||||
|
) as SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>;
|
||||||
|
});
|
||||||
|
|
||||||
// final spotify = ref.read(spotifyProvider);
|
ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id));
|
||||||
|
ref.invalidate(metadataPluginPlaylistTracksProvider(playlist.id));
|
||||||
|
}
|
||||||
|
|
||||||
// await spotify.invoke(
|
Future<void> addTracks(String playlistId, List<String> trackIds) async {
|
||||||
// (api) => api.playlists.addTracks(
|
if (state.value == null) return;
|
||||||
// trackIds.map((id) => 'spotify:track:$id').toList(),
|
|
||||||
// playlistId,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// ref.invalidate(playlistTracksProvider(playlistId));
|
await (await metadataPlugin)!
|
||||||
// }
|
.playlist
|
||||||
|
.addTracks(playlistId, trackIds: trackIds);
|
||||||
|
|
||||||
// Future<void> removeTracks(String playlistId, List<String> trackIds) async {
|
ref.invalidate(metadataPluginPlaylistTracksProvider(playlistId));
|
||||||
// if (state.value == null) return;
|
}
|
||||||
|
|
||||||
// final spotify = ref.read(spotifyProvider);
|
Future<void> removeTracks(String playlistId, List<String> trackIds) async {
|
||||||
|
if (state.value == null) return;
|
||||||
|
|
||||||
// await spotify.invoke(
|
await (await metadataPlugin)!
|
||||||
// (api) => api.playlists.removeTracks(
|
.playlist
|
||||||
// trackIds.map((id) => 'spotify:track:$id').toList(),
|
.removeTracks(playlistId, trackIds: trackIds);
|
||||||
// playlistId,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// ref.invalidate(playlistTracksProvider(playlistId));
|
ref.invalidate(metadataPluginPlaylistTracksProvider(playlistId));
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final metadataPluginSavedPlaylistsProvider = AsyncNotifierProvider<
|
final metadataPluginSavedPlaylistsProvider = AsyncNotifierProvider<
|
||||||
|
36
lib/provider/metadata_plugin/tracks/playlist.dart
Normal file
36
lib/provider/metadata_plugin/tracks/playlist.dart
Normal 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 PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||||
|
SpotubeFullTrackObject, String> {
|
||||||
|
PlaylistTracksNotifier() : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
fetch(offset, limit) async {
|
||||||
|
final tracks = await (await metadataPlugin)!.playlist.tracks(
|
||||||
|
arg,
|
||||||
|
offset: offset,
|
||||||
|
limit: limit,
|
||||||
|
);
|
||||||
|
|
||||||
|
return tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
build(arg) async {
|
||||||
|
ref.cacheFor();
|
||||||
|
|
||||||
|
ref.watch(metadataPluginProvider);
|
||||||
|
return await fetch(0, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final metadataPluginPlaylistTracksProvider = AutoDisposeAsyncNotifierProviderFamily<
|
||||||
|
PlaylistTracksNotifier,
|
||||||
|
SpotubePaginationResponseObject<SpotubeFullTrackObject>,
|
||||||
|
String>(
|
||||||
|
() => PlaylistTracksNotifier(),
|
||||||
|
);
|
44
lib/provider/metadata_plugin/utils/common.dart
Normal file
44
lib/provider/metadata_plugin/utils/common.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// ignore: implementation_imports
|
||||||
|
import 'package:riverpod/src/async_notifier.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
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/services/metadata/metadata.dart';
|
||||||
|
|
||||||
|
mixin MetadataPluginMixin<K>
|
||||||
|
// ignore: invalid_use_of_internal_member
|
||||||
|
on AsyncNotifierBase<SpotubePaginationResponseObject<K>> {
|
||||||
|
Future<MetadataPlugin?> get metadataPlugin async =>
|
||||||
|
await ref.read(metadataPluginProvider.future);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AutoDisposeAsyncNotifierCacheFor
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef {
|
||||||
|
// When invoked keeps your provider alive for [duration]
|
||||||
|
// ignore: unused_element
|
||||||
|
void cacheFor([Duration duration = const Duration(minutes: 5)]) {
|
||||||
|
final link = keepAlive();
|
||||||
|
final timer = Timer(duration, () => link.close());
|
||||||
|
onDispose(() => timer.cancel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
extension AutoDisposeCacheFor on AutoDisposeRef {
|
||||||
|
// When invoked keeps your provider alive for [duration]
|
||||||
|
// ignore: unused_element
|
||||||
|
void cacheFor([Duration duration = const Duration(minutes: 5)]) {
|
||||||
|
final link = keepAlive();
|
||||||
|
final timer = Timer(duration, () => link.close());
|
||||||
|
onDispose(() => timer.cancel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: subtype_of_sealed_class
|
||||||
|
class AsyncLoadingNext<T> extends AsyncData<T> {
|
||||||
|
const AsyncLoadingNext(super.value);
|
||||||
|
}
|
103
lib/provider/metadata_plugin/utils/family_paginated.dart
Normal file
103
lib/provider/metadata_plugin/utils/family_paginated.dart
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
|
// ignore: implementation_imports
|
||||||
|
import 'package:riverpod/src/async_notifier.dart';
|
||||||
|
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
|
||||||
|
|
||||||
|
abstract class FamilyPaginatedAsyncNotifier<K, A>
|
||||||
|
extends FamilyAsyncNotifier<SpotubePaginationResponseObject<K>, A>
|
||||||
|
with MetadataPluginMixin<K> {
|
||||||
|
Future<SpotubePaginationResponseObject<K>> fetch(int offset, int limit);
|
||||||
|
|
||||||
|
Future<void> fetchMore() async {
|
||||||
|
if (state.value == null || !state.value!.hasMore) return;
|
||||||
|
|
||||||
|
state = AsyncLoadingNext(state.asData!.value);
|
||||||
|
|
||||||
|
state = await AsyncValue.guard(
|
||||||
|
() async {
|
||||||
|
final newState = await fetch(
|
||||||
|
state.value!.nextOffset!,
|
||||||
|
state.value!.limit,
|
||||||
|
);
|
||||||
|
return newState.copyWith(items: [
|
||||||
|
...state.value!.items as List<K>,
|
||||||
|
...newState.items as List<K>,
|
||||||
|
]) as SpotubePaginationResponseObject<K>;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<K>> fetchAll() async {
|
||||||
|
if (state.value == null) return [];
|
||||||
|
if (!state.value!.hasMore) return state.value!.items as List<K>;
|
||||||
|
|
||||||
|
bool hasMore = true;
|
||||||
|
while (hasMore) {
|
||||||
|
await update((state) async {
|
||||||
|
final newState = await fetch(
|
||||||
|
state.nextOffset!,
|
||||||
|
state.limit,
|
||||||
|
);
|
||||||
|
|
||||||
|
hasMore = newState.hasMore;
|
||||||
|
return newState.copyWith(items: [
|
||||||
|
...state.items as List<K>,
|
||||||
|
...newState.items as List<K>,
|
||||||
|
]) as SpotubePaginationResponseObject<K>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.value!.items as List<K>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
|
||||||
|
extends AutoDisposeFamilyAsyncNotifier<SpotubePaginationResponseObject<K>,
|
||||||
|
A> with MetadataPluginMixin<K> {
|
||||||
|
Future<SpotubePaginationResponseObject<K>> fetch(int offset, int limit);
|
||||||
|
|
||||||
|
Future<void> fetchMore() async {
|
||||||
|
if (state.value == null || !state.value!.hasMore) return;
|
||||||
|
|
||||||
|
state = AsyncLoadingNext(state.asData!.value);
|
||||||
|
|
||||||
|
state = await AsyncValue.guard(
|
||||||
|
() async {
|
||||||
|
final newState = await fetch(
|
||||||
|
state.value!.nextOffset!,
|
||||||
|
state.value!.limit,
|
||||||
|
);
|
||||||
|
return newState.copyWith(items: [
|
||||||
|
...state.value!.items as List<K>,
|
||||||
|
...newState.items as List<K>,
|
||||||
|
]) as SpotubePaginationResponseObject<K>;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<K>> fetchAll() async {
|
||||||
|
if (state.value == null) return [];
|
||||||
|
if (!state.value!.hasMore) return state.value!.items as List<K>;
|
||||||
|
|
||||||
|
bool hasMore = true;
|
||||||
|
while (hasMore) {
|
||||||
|
await update((state) async {
|
||||||
|
final newState = await fetch(
|
||||||
|
state.nextOffset!,
|
||||||
|
state.limit,
|
||||||
|
);
|
||||||
|
|
||||||
|
hasMore = newState.hasMore;
|
||||||
|
return newState.copyWith(items: [
|
||||||
|
...state.items as List<K>,
|
||||||
|
...newState.items as List<K>,
|
||||||
|
]) as SpotubePaginationResponseObject<K>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.value!.items as List<K>;
|
||||||
|
}
|
||||||
|
}
|
@ -4,42 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/models/metadata/metadata.dart';
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:riverpod/src/async_notifier.dart';
|
import 'package:riverpod/src/async_notifier.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/metadata.dart';
|
|
||||||
|
|
||||||
mixin MetadataPluginMixin<K>
|
|
||||||
// ignore: invalid_use_of_internal_member
|
|
||||||
on AsyncNotifierBase<SpotubePaginationResponseObject<K>> {
|
|
||||||
Future<MetadataPlugin?> get metadataPlugin async =>
|
|
||||||
await ref.read(metadataPluginProvider.future);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
extension on AutoDisposeAsyncNotifierProviderRef {
|
|
||||||
// When invoked keeps your provider alive for [duration]
|
|
||||||
// ignore: unused_element
|
|
||||||
void cacheFor([Duration duration = const Duration(minutes: 5)]) {
|
|
||||||
final link = keepAlive();
|
|
||||||
final timer = Timer(duration, () => link.close());
|
|
||||||
onDispose(() => timer.cancel());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
extension on AutoDisposeRef {
|
|
||||||
// When invoked keeps your provider alive for [duration]
|
|
||||||
// ignore: unused_element
|
|
||||||
void cacheFor([Duration duration = const Duration(minutes: 5)]) {
|
|
||||||
final link = keepAlive();
|
|
||||||
final timer = Timer(duration, () => link.close());
|
|
||||||
onDispose(() => timer.cancel());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: subtype_of_sealed_class
|
|
||||||
class AsyncLoadingNext<T> extends AsyncData<T> {
|
|
||||||
const AsyncLoadingNext(super.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin PaginatedAsyncNotifierMixin<K>
|
mixin PaginatedAsyncNotifierMixin<K>
|
||||||
// ignore: invalid_use_of_internal_member
|
// ignore: invalid_use_of_internal_member
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
import 'package:hetu_script/hetu_script.dart';
|
||||||
|
import 'package:hetu_script/values.dart';
|
||||||
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
|
|
||||||
|
class MetadataPluginPlaylistEndpoint {
|
||||||
|
final Hetu hetu;
|
||||||
|
MetadataPluginPlaylistEndpoint(this.hetu);
|
||||||
|
|
||||||
|
HTInstance get hetuMetadataPlaylist =>
|
||||||
|
(hetu.fetch("metadataPlugin") as HTInstance).memberGet("playlist")
|
||||||
|
as HTInstance;
|
||||||
|
|
||||||
|
Future<SpotubeFullPlaylistObject> getPlaylist(String id) async {
|
||||||
|
final raw = await hetuMetadataPlaylist
|
||||||
|
.invoke("getPlaylist", positionalArgs: [id]) as Map;
|
||||||
|
|
||||||
|
return SpotubeFullPlaylistObject.fromJson(
|
||||||
|
raw.cast<String, dynamic>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SpotubePaginationResponseObject<SpotubeFullTrackObject>> tracks(
|
||||||
|
String id, {
|
||||||
|
int? offset,
|
||||||
|
int? limit,
|
||||||
|
}) async {
|
||||||
|
final raw = await hetuMetadataPlaylist.invoke(
|
||||||
|
"tracks",
|
||||||
|
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<SpotubeFullPlaylistObject?> create(
|
||||||
|
String userId, {
|
||||||
|
required String name,
|
||||||
|
String? description,
|
||||||
|
bool? public,
|
||||||
|
bool? collaborative,
|
||||||
|
}) async {
|
||||||
|
final raw = await hetuMetadataPlaylist.invoke(
|
||||||
|
"create",
|
||||||
|
positionalArgs: [userId],
|
||||||
|
namedArgs: {
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
"public": public,
|
||||||
|
"collaborative": collaborative,
|
||||||
|
}..removeWhere((key, value) => value == null),
|
||||||
|
) as Map?;
|
||||||
|
|
||||||
|
if (raw == null) return null;
|
||||||
|
|
||||||
|
return SpotubeFullPlaylistObject.fromJson(
|
||||||
|
raw.cast<String, dynamic>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> update(
|
||||||
|
String playlistId, {
|
||||||
|
String? name,
|
||||||
|
String? description,
|
||||||
|
bool? public,
|
||||||
|
bool? collaborative,
|
||||||
|
}) async {
|
||||||
|
await hetuMetadataPlaylist.invoke(
|
||||||
|
"update",
|
||||||
|
positionalArgs: [playlistId],
|
||||||
|
namedArgs: {
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
"public": public,
|
||||||
|
"collaborative": collaborative,
|
||||||
|
}..removeWhere((key, value) => value == null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addTracks(
|
||||||
|
String playlistId, {
|
||||||
|
required List<String> trackIds,
|
||||||
|
int? position,
|
||||||
|
}) async {
|
||||||
|
await hetuMetadataPlaylist.invoke(
|
||||||
|
"addTracks",
|
||||||
|
positionalArgs: [playlistId],
|
||||||
|
namedArgs: {
|
||||||
|
"trackIds": trackIds,
|
||||||
|
"position": position,
|
||||||
|
}..removeWhere((key, value) => value == null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeTracks(
|
||||||
|
String playlistId, {
|
||||||
|
required List<String> trackIds,
|
||||||
|
}) async {
|
||||||
|
await hetuMetadataPlaylist.invoke(
|
||||||
|
"removeTracks",
|
||||||
|
positionalArgs: [playlistId],
|
||||||
|
namedArgs: {
|
||||||
|
"trackIds": trackIds,
|
||||||
|
}..removeWhere((key, value) => value == null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save(String playlistId) async {
|
||||||
|
await hetuMetadataPlaylist.invoke(
|
||||||
|
"save",
|
||||||
|
positionalArgs: [playlistId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unsave(String playlistId) async {
|
||||||
|
await hetuMetadataPlaylist.invoke(
|
||||||
|
"unsave",
|
||||||
|
positionalArgs: [playlistId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deletePlaylist(String playlistId) async {
|
||||||
|
await hetuMetadataPlaylist.invoke(
|
||||||
|
"deletePlaylist",
|
||||||
|
positionalArgs: [playlistId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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/auth.dart';
|
import 'package:spotube/services/metadata/endpoints/auth.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';
|
||||||
|
|
||||||
const defaultMetadataLimit = "20";
|
const defaultMetadataLimit = "20";
|
||||||
@ -73,9 +74,11 @@ class MetadataPlugin {
|
|||||||
|
|
||||||
late final MetadataAuthEndpoint auth;
|
late final MetadataAuthEndpoint auth;
|
||||||
late final MetadataPluginUserEndpoint user;
|
late final MetadataPluginUserEndpoint user;
|
||||||
|
late final MetadataPluginPlaylistEndpoint playlist;
|
||||||
|
|
||||||
MetadataPlugin._(this.hetu) {
|
MetadataPlugin._(this.hetu) {
|
||||||
auth = MetadataAuthEndpoint(hetu);
|
auth = MetadataAuthEndpoint(hetu);
|
||||||
|
playlist = MetadataPluginPlaylistEndpoint(hetu);
|
||||||
user = MetadataPluginUserEndpoint(hetu);
|
user = MetadataPluginUserEndpoint(hetu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user