WIP Search
This commit is contained in:
parent
94eb363245
commit
a2ef7da707
5 changed files with 102 additions and 20 deletions
|
@ -10,7 +10,7 @@ extension BetterWhen<T> on AsyncValue<T> {
|
||||||
when(
|
when(
|
||||||
data: data,
|
data: data,
|
||||||
error: (error, stackTrace) =>
|
error: (error, stackTrace) =>
|
||||||
Text("error"), // TODO: Better err reporting
|
Text("$error"), // TODO: Better err reporting
|
||||||
loading: loading,
|
loading: loading,
|
||||||
skipLoadingOnRefresh: false,
|
skipLoadingOnRefresh: false,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
enum SearchType {
|
enum SearchType {
|
||||||
any,
|
any,
|
||||||
songs,
|
songs,
|
||||||
|
albums,
|
||||||
videos,
|
videos,
|
||||||
artists,
|
artists,
|
||||||
albums,
|
|
||||||
playlists,
|
playlists,
|
||||||
}
|
}
|
||||||
|
|
23
lib/providers/search_provider.dart
Normal file
23
lib/providers/search_provider.dart
Normal file
|
@ -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<IList<dynamic>> 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),
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,16 @@
|
||||||
import 'package:canal/models/search_type.dart';
|
import 'package:canal/helpers/extension_helper.dart';
|
||||||
import 'package:canal/widgets/select_button.dart';
|
import 'package:canal/providers/search_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:canal/widgets/thumbnail.dart';
|
||||||
import 'package:canal/models/tab.dart';
|
import 'package:dart_ytmusic_api/types.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.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});
|
const SearchTab({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -14,13 +20,65 @@ class SearchTab extends HookWidget implements TabPage {
|
||||||
String get title => "Search";
|
String get title => "Search";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ListView(
|
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),
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
children: [
|
children: [
|
||||||
SelectButton(
|
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,
|
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())),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,18 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
class SelectButton<T extends Enum> extends HookWidget {
|
class SelectButton<T extends Enum> extends HookWidget {
|
||||||
final T defaultValue;
|
final T value;
|
||||||
final List<T> values;
|
final List<T> values;
|
||||||
|
final Function(T newValue) onChanged;
|
||||||
const SelectButton({
|
const SelectButton({
|
||||||
required this.defaultValue,
|
required this.value,
|
||||||
required this.values,
|
required this.values,
|
||||||
|
required this.onChanged,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selected = useState(defaultValue);
|
|
||||||
final oldValue = useState(<T>{});
|
final oldValue = useState(<T>{});
|
||||||
return SegmentedButton(
|
return SegmentedButton(
|
||||||
segments: values
|
segments: values
|
||||||
|
@ -23,10 +24,10 @@ class SelectButton<T extends Enum> extends HookWidget {
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
onSelectionChanged: (newValue) {
|
onSelectionChanged: (newValue) {
|
||||||
selected.value = newValue.difference(oldValue.value).first;
|
onChanged(newValue.difference(oldValue.value).first);
|
||||||
oldValue.value = newValue;
|
oldValue.value = newValue;
|
||||||
},
|
},
|
||||||
selected: {selected.value},
|
selected: {value},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue