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 id,
required String name, required String name,
required String externalUri, required String externalUri,
List<SpotubeImageObject>? images,
}) = _SpotubeSimpleArtistObject; }) = _SpotubeSimpleArtistObject;
factory SpotubeSimpleArtistObject.fromJson(Map<String, dynamic> json) => factory SpotubeSimpleArtistObject.fromJson(Map<String, dynamic> json) =>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
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/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/metadata_plugin/search/all.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
class SearchPlaylistsSection extends HookConsumerWidget { class SearchPlaylistsSection extends HookConsumerWidget {
@ -12,17 +12,16 @@ class SearchPlaylistsSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final playlistsQuery = ref.watch(searchProvider(SearchType.playlist)); final searchTerm = ref.watch(searchTermStateProvider);
final playlistsQueryNotifier = final playlistsQuery =
ref.watch(searchProvider(SearchType.playlist).notifier); ref.watch(metadataPluginSearchAllProvider(searchTerm));
final playlists = final playlists = playlistsQuery.asData?.value.playlists ?? [];
playlistsQuery.asData?.value.items.cast<PlaylistSimple>() ?? [];
return HorizontalPlaybuttonCardView( return HorizontalPlaybuttonCardView(
isLoadingNextPage: playlistsQuery.isLoadingNextPage, isLoadingNextPage: false,
hasNextPage: playlistsQuery.asData?.value.hasMore == true, hasNextPage: false,
items: playlists, items: playlists,
onFetchMore: playlistsQueryNotifier.fetchMore, onFetchMore: () {},
title: Text(context.l10n.playlists), 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/auth.dart';
import 'package:spotube/services/metadata/endpoints/browse.dart'; import 'package:spotube/services/metadata/endpoints/browse.dart';
import 'package:spotube/services/metadata/endpoints/playlist.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'; import 'package:spotube/services/metadata/endpoints/user.dart';
const defaultMetadataLimit = "20"; const defaultMetadataLimit = "20";
@ -80,6 +81,7 @@ class MetadataPlugin {
late final MetadataPluginAlbumEndpoint album; late final MetadataPluginAlbumEndpoint album;
late final MetadataPluginArtistEndpoint artist; late final MetadataPluginArtistEndpoint artist;
late final MetadataPluginBrowseEndpoint browse; late final MetadataPluginBrowseEndpoint browse;
late final MetadataPluginSearchEndpoint search;
late final MetadataPluginPlaylistEndpoint playlist; late final MetadataPluginPlaylistEndpoint playlist;
late final MetadataPluginUserEndpoint user; late final MetadataPluginUserEndpoint user;
@ -89,6 +91,7 @@ class MetadataPlugin {
artist = MetadataPluginArtistEndpoint(hetu); artist = MetadataPluginArtistEndpoint(hetu);
album = MetadataPluginAlbumEndpoint(hetu); album = MetadataPluginAlbumEndpoint(hetu);
browse = MetadataPluginBrowseEndpoint(hetu); browse = MetadataPluginBrowseEndpoint(hetu);
search = MetadataPluginSearchEndpoint(hetu);
playlist = MetadataPluginPlaylistEndpoint(hetu); playlist = MetadataPluginPlaylistEndpoint(hetu);
user = MetadataPluginUserEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu);
} }