refactor: search to use metadata plugin

This commit is contained in:
Kingkor Roy Tirtho 2025-06-16 09:52:02 +06:00
parent 7569c37739
commit fcc05a4243
11 changed files with 277 additions and 70 deletions

View File

@ -21,6 +21,7 @@ class SpotubeSimpleArtistObject with _$SpotubeSimpleArtistObject {
required String id,
required String name,
required String externalUri,
List<SpotubeImageObject>? images,
}) = _SpotubeSimpleArtistObject;
factory SpotubeSimpleArtistObject.fromJson(Map<String, dynamic> json) =>

View File

@ -994,6 +994,7 @@ mixin _$SpotubeSimpleArtistObject {
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get externalUri => throw _privateConstructorUsedError;
List<SpotubeImageObject>? get images => throw _privateConstructorUsedError;
/// Serializes this SpotubeSimpleArtistObject to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -1011,7 +1012,11 @@ abstract class $SpotubeSimpleArtistObjectCopyWith<$Res> {
$Res Function(SpotubeSimpleArtistObject) then) =
_$SpotubeSimpleArtistObjectCopyWithImpl<$Res, SpotubeSimpleArtistObject>;
@useResult
$Res call({String id, String name, String externalUri});
$Res call(
{String id,
String name,
String externalUri,
List<SpotubeImageObject>? images});
}
/// @nodoc
@ -1033,6 +1038,7 @@ class _$SpotubeSimpleArtistObjectCopyWithImpl<$Res,
Object? id = null,
Object? name = null,
Object? externalUri = null,
Object? images = freezed,
}) {
return _then(_value.copyWith(
id: null == id
@ -1047,6 +1053,10 @@ class _$SpotubeSimpleArtistObjectCopyWithImpl<$Res,
? _value.externalUri
: externalUri // ignore: cast_nullable_to_non_nullable
as String,
images: freezed == images
? _value.images
: images // ignore: cast_nullable_to_non_nullable
as List<SpotubeImageObject>?,
) as $Val);
}
}
@ -1060,7 +1070,11 @@ abstract class _$$SpotubeSimpleArtistObjectImplCopyWith<$Res>
__$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String id, String name, String externalUri});
$Res call(
{String id,
String name,
String externalUri,
List<SpotubeImageObject>? images});
}
/// @nodoc
@ -1081,6 +1095,7 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res>
Object? id = null,
Object? name = null,
Object? externalUri = null,
Object? images = freezed,
}) {
return _then(_$SpotubeSimpleArtistObjectImpl(
id: null == id
@ -1095,6 +1110,10 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res>
? _value.externalUri
: externalUri // ignore: cast_nullable_to_non_nullable
as String,
images: freezed == images
? _value._images
: images // ignore: cast_nullable_to_non_nullable
as List<SpotubeImageObject>?,
));
}
}
@ -1103,7 +1122,11 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res>
@JsonSerializable()
class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject {
_$SpotubeSimpleArtistObjectImpl(
{required this.id, required this.name, required this.externalUri});
{required this.id,
required this.name,
required this.externalUri,
final List<SpotubeImageObject>? images})
: _images = images;
factory _$SpotubeSimpleArtistObjectImpl.fromJson(Map<String, dynamic> json) =>
_$$SpotubeSimpleArtistObjectImplFromJson(json);
@ -1114,10 +1137,19 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject {
final String name;
@override
final String externalUri;
final List<SpotubeImageObject>? _images;
@override
List<SpotubeImageObject>? get images {
final value = _images;
if (value == null) return null;
if (_images is EqualUnmodifiableListView) return _images;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'SpotubeSimpleArtistObject(id: $id, name: $name, externalUri: $externalUri)';
return 'SpotubeSimpleArtistObject(id: $id, name: $name, externalUri: $externalUri, images: $images)';
}
@override
@ -1128,12 +1160,14 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject {
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.externalUri, externalUri) ||
other.externalUri == externalUri));
other.externalUri == externalUri) &&
const DeepCollectionEquality().equals(other._images, _images));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, name, externalUri);
int get hashCode => Object.hash(runtimeType, id, name, externalUri,
const DeepCollectionEquality().hash(_images));
/// Create a copy of SpotubeSimpleArtistObject
/// with the given fields replaced by the non-null parameter values.
@ -1154,9 +1188,11 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject {
abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject {
factory _SpotubeSimpleArtistObject(
{required final String id,
required final String name,
required final String externalUri}) = _$SpotubeSimpleArtistObjectImpl;
{required final String id,
required final String name,
required final String externalUri,
final List<SpotubeImageObject>? images}) =
_$SpotubeSimpleArtistObjectImpl;
factory _SpotubeSimpleArtistObject.fromJson(Map<String, dynamic> json) =
_$SpotubeSimpleArtistObjectImpl.fromJson;
@ -1167,6 +1203,8 @@ abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject {
String get name;
@override
String get externalUri;
@override
List<SpotubeImageObject>? get images;
/// Create a copy of SpotubeSimpleArtistObject
/// with the given fields replaced by the non-null parameter values.
@ -2529,7 +2567,7 @@ SpotubeSearchResponseObject _$SpotubeSearchResponseObjectFromJson(
mixin _$SpotubeSearchResponseObject {
List<SpotubeSimpleAlbumObject> get albums =>
throw _privateConstructorUsedError;
List<SpotubeSimpleArtistObject> get artists =>
List<SpotubeFullArtistObject> get artists =>
throw _privateConstructorUsedError;
List<SpotubeSimplePlaylistObject> get playlists =>
throw _privateConstructorUsedError;
@ -2556,7 +2594,7 @@ abstract class $SpotubeSearchResponseObjectCopyWith<$Res> {
@useResult
$Res call(
{List<SpotubeSimpleAlbumObject> albums,
List<SpotubeSimpleArtistObject> artists,
List<SpotubeFullArtistObject> artists,
List<SpotubeSimplePlaylistObject> playlists,
List<SpotubeSimpleTrackObject> tracks});
}
@ -2590,7 +2628,7 @@ class _$SpotubeSearchResponseObjectCopyWithImpl<$Res,
artists: null == artists
? _value.artists
: artists // ignore: cast_nullable_to_non_nullable
as List<SpotubeSimpleArtistObject>,
as List<SpotubeFullArtistObject>,
playlists: null == playlists
? _value.playlists
: playlists // ignore: cast_nullable_to_non_nullable
@ -2614,7 +2652,7 @@ abstract class _$$SpotubeSearchResponseObjectImplCopyWith<$Res>
@useResult
$Res call(
{List<SpotubeSimpleAlbumObject> albums,
List<SpotubeSimpleArtistObject> artists,
List<SpotubeFullArtistObject> artists,
List<SpotubeSimplePlaylistObject> playlists,
List<SpotubeSimpleTrackObject> tracks});
}
@ -2647,7 +2685,7 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res>
artists: null == artists
? _value._artists
: artists // ignore: cast_nullable_to_non_nullable
as List<SpotubeSimpleArtistObject>,
as List<SpotubeFullArtistObject>,
playlists: null == playlists
? _value._playlists
: playlists // ignore: cast_nullable_to_non_nullable
@ -2666,7 +2704,7 @@ class _$SpotubeSearchResponseObjectImpl
implements _SpotubeSearchResponseObject {
_$SpotubeSearchResponseObjectImpl(
{required final List<SpotubeSimpleAlbumObject> albums,
required final List<SpotubeSimpleArtistObject> artists,
required final List<SpotubeFullArtistObject> artists,
required final List<SpotubeSimplePlaylistObject> playlists,
required final List<SpotubeSimpleTrackObject> tracks})
: _albums = albums,
@ -2686,9 +2724,9 @@ class _$SpotubeSearchResponseObjectImpl
return EqualUnmodifiableListView(_albums);
}
final List<SpotubeSimpleArtistObject> _artists;
final List<SpotubeFullArtistObject> _artists;
@override
List<SpotubeSimpleArtistObject> get artists {
List<SpotubeFullArtistObject> get artists {
if (_artists is EqualUnmodifiableListView) return _artists;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_artists);
@ -2757,7 +2795,7 @@ abstract class _SpotubeSearchResponseObject
implements SpotubeSearchResponseObject {
factory _SpotubeSearchResponseObject(
{required final List<SpotubeSimpleAlbumObject> albums,
required final List<SpotubeSimpleArtistObject> artists,
required final List<SpotubeFullArtistObject> artists,
required final List<SpotubeSimplePlaylistObject> playlists,
required final List<SpotubeSimpleTrackObject> tracks}) =
_$SpotubeSearchResponseObjectImpl;
@ -2768,7 +2806,7 @@ abstract class _SpotubeSearchResponseObject
@override
List<SpotubeSimpleAlbumObject> get albums;
@override
List<SpotubeSimpleArtistObject> get artists;
List<SpotubeFullArtistObject> get artists;
@override
List<SpotubeSimplePlaylistObject> get playlists;
@override

View File

@ -113,6 +113,10 @@ _$SpotubeSimpleArtistObjectImpl _$$SpotubeSimpleArtistObjectImplFromJson(
id: json['id'] as String,
name: json['name'] as String,
externalUri: json['externalUri'] as String,
images: (json['images'] as List<dynamic>?)
?.map((e) =>
SpotubeImageObject.fromJson(Map<String, dynamic>.from(e as Map)))
.toList(),
);
Map<String, dynamic> _$$SpotubeSimpleArtistObjectImplToJson(
@ -121,6 +125,7 @@ Map<String, dynamic> _$$SpotubeSimpleArtistObjectImplToJson(
'id': instance.id,
'name': instance.name,
'externalUri': instance.externalUri,
'images': instance.images?.map((e) => e.toJson()).toList(),
};
_$SpotubeBrowseSectionObjectImpl<T>
@ -260,7 +265,7 @@ _$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
artists: (json['artists'] as List<dynamic>)
.map((e) => SpotubeSimpleArtistObject.fromJson(
.map((e) => SpotubeFullArtistObject.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
playlists: (json['playlists'] as List<dynamic>)

View File

@ -4,7 +4,7 @@ part of 'metadata.dart';
class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject {
factory SpotubeSearchResponseObject({
required List<SpotubeSimpleAlbumObject> albums,
required List<SpotubeSimpleArtistObject> artists,
required List<SpotubeFullArtistObject> artists,
required List<SpotubeSimplePlaylistObject> playlists,
required List<SpotubeSimpleTrackObject> tracks,
}) = _SpotubeSearchResponseObject;

View File

@ -2,7 +2,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_undraw/flutter_undraw.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotify/spotify.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -18,8 +17,8 @@ import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dar
import 'package:spotube/pages/search/sections/albums.dart';
import 'package:spotube/pages/search/sections/artists.dart';
import 'package:spotube/pages/search/sections/playlists.dart';
import 'package:spotube/pages/search/sections/tracks.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/metadata_plugin/auth.dart';
import 'package:spotube/provider/metadata_plugin/search/all.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:auto_route/auto_route.dart';
@ -39,17 +38,11 @@ class SearchPage extends HookConsumerWidget {
final controller = useShadcnTextEditingController();
final focusNode = useFocusNode();
final auth = ref.watch(authenticationProvider);
final authenticated = ref.watch(metadataPluginAuthenticatedProvider);
final searchTerm = ref.watch(searchTermStateProvider);
final searchTrack = ref.watch(searchProvider(SearchType.track));
final searchAlbum = ref.watch(searchProvider(SearchType.album));
final searchPlaylist = ref.watch(searchProvider(SearchType.playlist));
final searchArtist = ref.watch(searchProvider(SearchType.artist));
final queries = [searchTrack, searchAlbum, searchPlaylist, searchArtist];
final isFetching = queries.every((s) => s.isLoading);
final searchSnapshot =
ref.watch(metadataPluginSearchAllProvider(searchTerm));
useEffect(() {
controller.text = searchTerm;
@ -82,7 +75,7 @@ class SearchPage extends HookConsumerWidget {
if (kTitlebarVisible)
const TitleBar(automaticallyImplyLeading: false, height: 30)
],
child: auth.asData?.value == null
child: authenticated.asData?.value != true
? const AnonymousFallback()
: Column(
children: [
@ -174,7 +167,10 @@ class SearchPage extends HookConsumerWidget {
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: switch ((searchTerm.isEmpty, isFetching)) {
child: switch ((
searchTerm.isEmpty,
searchSnapshot.isLoading
)) {
(true, false) => Column(
children: [
SizedBox(
@ -228,7 +224,7 @@ class SearchPage extends HookConsumerWidget {
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SearchTracksSection(),
// SearchTracksSection(),
SearchPlaylistsSection(),
Gap(20),
SearchArtistsSection(),

View File

@ -1,11 +1,9 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/extensions/album_simple.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/metadata_plugin/search/all.dart';
import 'package:spotube/provider/spotify/spotify.dart';
class SearchAlbumsSection extends HookConsumerWidget {
@ -15,23 +13,15 @@ class SearchAlbumsSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final query = ref.watch(searchProvider(SearchType.album));
final notifier = ref.watch(searchProvider(SearchType.album).notifier);
final albums = useMemoized(
() =>
query.asData?.value.items
.cast<AlbumSimple>()
.map((e) => e.toAlbum())
.toList() ??
[],
[query.asData?.value],
);
final searchTerm = ref.watch(searchTermStateProvider);
final search = ref.watch(metadataPluginSearchAllProvider(searchTerm));
final albums = search.asData?.value.albums ?? [];
return HorizontalPlaybuttonCardView(
isLoadingNextPage: query.isLoadingNextPage,
hasNextPage: query.asData?.value.hasMore == true,
isLoadingNextPage: false,
hasNextPage: false,
items: albums,
onFetchMore: notifier.fetchMore,
onFetchMore: () {},
title: Text(context.l10n.albums),
);
}

View File

@ -1,9 +1,9 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/metadata_plugin/search/all.dart';
import 'package:spotube/provider/spotify/spotify.dart';
class SearchArtistsSection extends HookConsumerWidget {
@ -13,16 +13,16 @@ class SearchArtistsSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final query = ref.watch(searchProvider(SearchType.artist));
final notifier = ref.watch(searchProvider(SearchType.artist).notifier);
final searchTerm = ref.watch(searchTermStateProvider);
final search = ref.watch(metadataPluginSearchAllProvider(searchTerm));
final artists = query.asData?.value.items.cast<Artist>() ?? [];
final artists = search.asData?.value.artists ?? [];
return HorizontalPlaybuttonCardView<Artist>(
isLoadingNextPage: query.isLoadingNextPage,
hasNextPage: query.asData?.value.hasMore == true,
return HorizontalPlaybuttonCardView(
isLoadingNextPage: false,
hasNextPage: false,
items: artists,
onFetchMore: notifier.fetchMore,
onFetchMore: () {},
title: Text(context.l10n.artists),
);
}

View File

@ -1,8 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/metadata_plugin/search/all.dart';
import 'package:spotube/provider/spotify/spotify.dart';
class SearchPlaylistsSection extends HookConsumerWidget {
@ -12,17 +12,16 @@ class SearchPlaylistsSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final playlistsQuery = ref.watch(searchProvider(SearchType.playlist));
final playlistsQueryNotifier =
ref.watch(searchProvider(SearchType.playlist).notifier);
final playlists =
playlistsQuery.asData?.value.items.cast<PlaylistSimple>() ?? [];
final searchTerm = ref.watch(searchTermStateProvider);
final playlistsQuery =
ref.watch(metadataPluginSearchAllProvider(searchTerm));
final playlists = playlistsQuery.asData?.value.playlists ?? [];
return HorizontalPlaybuttonCardView(
isLoadingNextPage: playlistsQuery.isLoadingNextPage,
hasNextPage: playlistsQuery.asData?.value.hasMore == true,
isLoadingNextPage: false,
hasNextPage: false,
items: playlists,
onFetchMore: playlistsQueryNotifier.fetchMore,
onFetchMore: () {},
title: Text(context.l10n.playlists),
);
}

View File

@ -0,0 +1,19 @@
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/endpoints/error.dart';
final metadataPluginSearchAllProvider =
FutureProvider.autoDispose.family<SpotubeSearchResponseObject, String>(
(ref, query) async {
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
if (metadataPlugin == null) {
throw MetadataPluginException.noDefaultPlugin(
"No default metadata plugin found",
);
}
return metadataPlugin.search.all(query);
},
);

View File

@ -0,0 +1,156 @@
import 'package:hetu_script/hetu_script.dart';
import 'package:hetu_script/values.dart';
import 'package:spotube/models/metadata/metadata.dart';
class MetadataPluginSearchEndpoint {
final Hetu hetu;
MetadataPluginSearchEndpoint(this.hetu);
HTInstance get hetuMetadataSearch =>
(hetu.fetch("metadataPlugin") as HTInstance).memberGet("search")
as HTInstance;
Future<SpotubeSearchResponseObject> all(String query) async {
if (query.isEmpty) {
return SpotubeSearchResponseObject(
albums: [],
artists: [],
playlists: [],
tracks: [],
);
}
final raw = await hetuMetadataSearch.invoke(
"all",
positionalArgs: [query],
) as Map;
return SpotubeSearchResponseObject.fromJson(raw.cast<String, dynamic>());
}
Future<SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>> albums(
String query, {
int? limit,
int? offset,
}) async {
if (query.isEmpty) {
return SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>(
items: [],
total: 0,
limit: limit ?? 20,
hasMore: false,
nextOffset: null,
);
}
final raw = await hetuMetadataSearch.invoke(
"albums",
positionalArgs: [query],
namedArgs: {
"limit": limit,
"offset": offset,
}..removeWhere((key, value) => value == null),
) as Map;
return SpotubePaginationResponseObject<SpotubeSimpleAlbumObject>.fromJson(
raw.cast<String, dynamic>(),
(json) => SpotubeSimpleAlbumObject.fromJson(json.cast<String, dynamic>()),
);
}
Future<SpotubePaginationResponseObject<SpotubeFullArtistObject>> artists(
String query, {
int? limit,
int? offset,
}) async {
if (query.isEmpty) {
return SpotubePaginationResponseObject<SpotubeFullArtistObject>(
items: [],
total: 0,
limit: limit ?? 20,
hasMore: false,
nextOffset: null,
);
}
final raw = await hetuMetadataSearch.invoke(
"artists",
positionalArgs: [query],
namedArgs: {
"limit": limit,
"offset": offset,
}..removeWhere((key, value) => value == null),
) as Map;
return SpotubePaginationResponseObject<SpotubeFullArtistObject>.fromJson(
raw.cast<String, dynamic>(),
(json) => SpotubeFullArtistObject.fromJson(
json.cast<String, dynamic>(),
),
);
}
Future<SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>>
playlists(
String query, {
int? limit,
int? offset,
}) async {
if (query.isEmpty) {
return SpotubePaginationResponseObject<SpotubeSimplePlaylistObject>(
items: [],
total: 0,
limit: limit ?? 20,
hasMore: false,
nextOffset: null,
);
}
final raw = await hetuMetadataSearch.invoke(
"playlists",
positionalArgs: [query],
namedArgs: {
"limit": limit,
"offset": offset,
}..removeWhere((key, value) => value == null),
) as Map;
return SpotubePaginationResponseObject<
SpotubeSimplePlaylistObject>.fromJson(
raw.cast<String, dynamic>(),
(json) => SpotubeSimplePlaylistObject.fromJson(
json.cast<String, dynamic>(),
),
);
}
Future<SpotubePaginationResponseObject<SpotubeSimpleTrackObject>> tracks(
String query, {
int? limit,
int? offset,
}) async {
if (query.isEmpty) {
return SpotubePaginationResponseObject<SpotubeSimpleTrackObject>(
items: [],
total: 0,
limit: limit ?? 20,
hasMore: false,
nextOffset: null,
);
}
final raw = await hetuMetadataSearch.invoke(
"tracks",
positionalArgs: [query],
namedArgs: {
"limit": limit,
"offset": offset,
}..removeWhere((key, value) => value == null),
) as Map;
return SpotubePaginationResponseObject<SpotubeSimpleTrackObject>.fromJson(
raw.cast<String, dynamic>(),
(json) => SpotubeSimpleTrackObject.fromJson(json.cast<String, dynamic>()),
);
}
}

View File

@ -16,6 +16,7 @@ import 'package:spotube/services/metadata/endpoints/artist.dart';
import 'package:spotube/services/metadata/endpoints/auth.dart';
import 'package:spotube/services/metadata/endpoints/browse.dart';
import 'package:spotube/services/metadata/endpoints/playlist.dart';
import 'package:spotube/services/metadata/endpoints/search.dart';
import 'package:spotube/services/metadata/endpoints/user.dart';
const defaultMetadataLimit = "20";
@ -80,6 +81,7 @@ class MetadataPlugin {
late final MetadataPluginAlbumEndpoint album;
late final MetadataPluginArtistEndpoint artist;
late final MetadataPluginBrowseEndpoint browse;
late final MetadataPluginSearchEndpoint search;
late final MetadataPluginPlaylistEndpoint playlist;
late final MetadataPluginUserEndpoint user;
@ -89,6 +91,7 @@ class MetadataPlugin {
artist = MetadataPluginArtistEndpoint(hetu);
album = MetadataPluginAlbumEndpoint(hetu);
browse = MetadataPluginBrowseEndpoint(hetu);
search = MetadataPluginSearchEndpoint(hetu);
playlist = MetadataPluginPlaylistEndpoint(hetu);
user = MetadataPluginUserEndpoint(hetu);
}