From a2ef7da70780e7789bead032c5da28c073aa1e78 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 3 Jan 2025 20:59:59 -0500 Subject: [PATCH] WIP Search --- lib/helpers/extension_helper.dart | 2 +- lib/models/search_type.dart | 2 +- lib/providers/search_provider.dart | 23 ++++++++ lib/screens/tabs/search.dart | 84 +++++++++++++++++++++++++----- lib/widgets/select_button.dart | 11 ++-- 5 files changed, 102 insertions(+), 20 deletions(-) create mode 100644 lib/providers/search_provider.dart diff --git a/lib/helpers/extension_helper.dart b/lib/helpers/extension_helper.dart index 4e0906f..beabf0c 100644 --- a/lib/helpers/extension_helper.dart +++ b/lib/helpers/extension_helper.dart @@ -10,7 +10,7 @@ extension BetterWhen on AsyncValue { when( data: data, error: (error, stackTrace) => - Text("error"), // TODO: Better err reporting + Text("$error"), // TODO: Better err reporting loading: loading, skipLoadingOnRefresh: false, ); diff --git a/lib/models/search_type.dart b/lib/models/search_type.dart index fa60af8..7097960 100644 --- a/lib/models/search_type.dart +++ b/lib/models/search_type.dart @@ -1,8 +1,8 @@ enum SearchType { any, songs, + albums, videos, artists, - albums, playlists, } diff --git a/lib/providers/search_provider.dart b/lib/providers/search_provider.dart new file mode 100644 index 0000000..843e48e --- /dev/null +++ b/lib/providers/search_provider.dart @@ -0,0 +1,23 @@ +import 'package:canal/models/search_type.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:canal/providers/ytmusic_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +part "search_provider.g.dart"; + +@riverpod +Future> searchProvider( + Ref ref, { + required String search, + required SearchType searchType, +}) async { + final yt = await ytmusic(ref); + return IList(switch (searchType) { + SearchType.any => await yt.search(search), + SearchType.songs => await yt.searchSongs(search), + SearchType.albums => await yt.searchAlbums(search), + SearchType.videos => await yt.searchVideos(search), + SearchType.artists => await yt.searchAlbums(search), + SearchType.playlists => await yt.searchPlaylists(search), + }); +} diff --git a/lib/screens/tabs/search.dart b/lib/screens/tabs/search.dart index e206364..2f6cc7a 100644 --- a/lib/screens/tabs/search.dart +++ b/lib/screens/tabs/search.dart @@ -1,10 +1,16 @@ -import 'package:canal/models/search_type.dart'; -import 'package:canal/widgets/select_button.dart'; -import 'package:flutter/material.dart'; -import 'package:canal/models/tab.dart'; +import 'package:canal/helpers/extension_helper.dart'; +import 'package:canal/providers/search_provider.dart'; +import 'package:canal/widgets/thumbnail.dart'; +import 'package:dart_ytmusic_api/types.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:canal/widgets/select_button.dart'; +import 'package:canal/models/search_type.dart'; +import 'package:canal/models/tab.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:yaru/yaru.dart'; -class SearchTab extends HookWidget implements TabPage { +class SearchTab extends HookConsumerWidget implements TabPage { const SearchTab({super.key}); @override @@ -14,13 +20,65 @@ class SearchTab extends HookWidget implements TabPage { String get title => "Search"; @override - Widget build(BuildContext context) => ListView( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), - children: [ - SelectButton( + Widget build(BuildContext context, WidgetRef ref) { + final type = useState(SearchType.any); + final search = useState(""); + final debouncedSearch = useDebounced(search, Duration(milliseconds: 250)); + + return ListView( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + children: [ + YaruSearchField( + hintText: "Search YouTube music...", + fillColor: Theme.of(context).colorScheme.surface, + onChanged: (value) => search.value = value, + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: SelectButton( + value: type.value, values: SearchType.values, - defaultValue: SearchType.any, - ) - ], - ); + onChanged: (value) => type.value = value, + ), + ), + Divider(), + SizedBox(height: 8), + ref + .watch(searchProviderProvider( + search: search.value, searchType: type.value)) + .betterWhen( + data: (results) => Wrap( + children: results + .map((result) => SizedBox( + height: 64, + width: 64, + child: switch (result) { + SongDetailed _ => Thumbnail( + url: result.thumbnails.first.url, + onClick: () {}, + ), + AlbumDetailed _ => Thumbnail( + url: result.thumbnails.first.url, + onClick: () {}, + ), + VideoDetailed _ => Thumbnail( + url: result.thumbnails.first.url, + onClick: () {}, + ), + ArtistDetailed _ => Thumbnail( + url: result.thumbnails.first.url, + onClick: () {}, + ), + PlaylistDetailed _ => Thumbnail( + url: result.thumbnails.first.url, + onClick: () {}, + ), + _ => throw Exception( + "Unknown Detailed Result: ${result.runtimeType}", + ), + })) + .toList())), + ], + ); + } } diff --git a/lib/widgets/select_button.dart b/lib/widgets/select_button.dart index 32bfb98..e84591a 100644 --- a/lib/widgets/select_button.dart +++ b/lib/widgets/select_button.dart @@ -3,17 +3,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; class SelectButton extends HookWidget { - final T defaultValue; + final T value; final List values; + final Function(T newValue) onChanged; const SelectButton({ - required this.defaultValue, + required this.value, required this.values, + required this.onChanged, super.key, }); @override Widget build(BuildContext context) { - final selected = useState(defaultValue); final oldValue = useState({}); return SegmentedButton( segments: values @@ -23,10 +24,10 @@ class SelectButton extends HookWidget { )) .toList(), onSelectionChanged: (newValue) { - selected.value = newValue.difference(oldValue.value).first; + onChanged(newValue.difference(oldValue.value).first); oldValue.value = newValue; }, - selected: {selected.value}, + selected: {value}, ); } }