119 lines
3.6 KiB
Dart
119 lines
3.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../state/providers.dart';
|
|
import '../theme/tokens.dart';
|
|
import 'browser_screen.dart';
|
|
|
|
/// Search — `search3` across artists / albums / songs.
|
|
class SearchScreen extends ConsumerStatefulWidget {
|
|
const SearchScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<SearchScreen> createState() => _SearchScreenState();
|
|
}
|
|
|
|
class _SearchScreenState extends ConsumerState<SearchScreen> {
|
|
final _controller = TextEditingController();
|
|
String _query = '';
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accent = Theme.of(context).colorScheme.primary;
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: TextField(
|
|
controller: _controller,
|
|
autofocus: true,
|
|
textInputAction: TextInputAction.search,
|
|
style: const TextStyle(color: TimbreColors.foreground),
|
|
cursorColor: accent,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Search artists, albums, songs…',
|
|
hintStyle: TextStyle(color: TimbreColors.dimmed),
|
|
border: InputBorder.none,
|
|
),
|
|
onSubmitted: (v) => setState(() => _query = v),
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: _query.trim().isEmpty
|
|
? const Center(
|
|
child: Text('Type and press search.',
|
|
style: TextStyle(color: TimbreColors.dimmed)),
|
|
)
|
|
: _Results(query: _query),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Results extends ConsumerWidget {
|
|
const _Results({required this.query});
|
|
|
|
final String query;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final results = ref.watch(searchProvider(query));
|
|
return results.when(
|
|
loading: () => const Center(
|
|
child: SizedBox(
|
|
height: 20,
|
|
width: 20,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
),
|
|
error: (e, _) => Center(
|
|
child: Text('$e', style: const TextStyle(color: TimbreColors.dimmed)),
|
|
),
|
|
data: (r) {
|
|
if (r.artists.isEmpty && r.albums.isEmpty && r.songs.isEmpty) {
|
|
return const Center(
|
|
child: Text('No results.',
|
|
style: TextStyle(color: TimbreColors.dimmed)),
|
|
);
|
|
}
|
|
return ListView(
|
|
children: [
|
|
for (final a in r.artists)
|
|
BrowseRow(
|
|
title: a.name ?? 'Unknown artist',
|
|
onTap: () => Navigator.of(context).push(
|
|
MaterialPageRoute(builder: (_) => ArtistScreen(id: a.id)),
|
|
),
|
|
),
|
|
for (final a in r.albums)
|
|
BrowseRow(
|
|
title: a.name ?? 'Unknown album',
|
|
trailing: a.artist,
|
|
onTap: () => Navigator.of(context).push(
|
|
MaterialPageRoute(builder: (_) => AlbumScreen(id: a.id)),
|
|
),
|
|
),
|
|
for (var i = 0; i < r.songs.length; i++)
|
|
BrowseRow(
|
|
title: r.songs[i].title ?? 'Untitled',
|
|
trailing: r.songs[i].artist,
|
|
onTap: () => ref
|
|
.read(playbackCommandsProvider)
|
|
.playSongs(r.songs, startIndex: i),
|
|
onPlayNext: () => ref
|
|
.read(playbackCommandsProvider)
|
|
.playNext(r.songs[i]),
|
|
onAddToQueue: () => ref
|
|
.read(playbackCommandsProvider)
|
|
.addToQueue(r.songs[i]),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|