import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../library/widgets/album_card.dart'; import '../library/widgets/artist_card.dart'; import '../library/widgets/track_row.dart'; import '../models/search_response.dart'; import '../player/player_provider.dart'; import '../shared/widgets/main_app_bar_actions.dart'; import '../theme/theme_extension.dart'; import 'search_provider.dart'; class SearchScreen extends ConsumerStatefulWidget { const SearchScreen({super.key}); @override ConsumerState createState() => _SearchScreenState(); } class _SearchScreenState extends ConsumerState { final _controller = TextEditingController(); final _focus = FocusNode(); @override void initState() { super.initState(); // Autofocus the field on screen entry; keyboard pops up so the // user can start typing immediately. WidgetsBinding.instance.addPostFrameCallback((_) => _focus.requestFocus()); } @override void dispose() { _controller.dispose(); _focus.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final fs = Theme.of(context).extension()!; final results = ref.watch(searchResultsProvider); return Scaffold( backgroundColor: fs.obsidian, appBar: AppBar( backgroundColor: fs.obsidian, elevation: 0, leading: IconButton( icon: Icon(LucideIcons.arrow_left, color: fs.parchment), onPressed: () => context.pop(), ), title: TextField( controller: _controller, focusNode: _focus, autofocus: true, style: TextStyle(color: fs.parchment), cursorColor: fs.accent, decoration: InputDecoration( hintText: 'Search artists, albums, tracks', hintStyle: TextStyle(color: fs.ash), border: InputBorder.none, ), onChanged: (v) => ref.read(searchQueryProvider.notifier).set(v), textInputAction: TextInputAction.search, ), actions: [ if (_controller.text.isNotEmpty) IconButton( icon: Icon(LucideIcons.x, color: fs.ash), onPressed: () { _controller.clear(); ref.read(searchQueryProvider.notifier).set(''); _focus.requestFocus(); }, ), const MainAppBarActions(currentRoute: '/search'), ], ), body: results.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center( child: Padding( padding: const EdgeInsets.all(24), child: Text('$e', style: TextStyle(color: fs.error)), ), ), data: (r) => r == null ? const _Hint(message: 'Type to search your library.') : r.isEmpty ? const _Hint(message: 'No matches for that query.') : _Results(results: r), ), ); } } class _Hint extends StatelessWidget { const _Hint({required this.message}); final String message; @override Widget build(BuildContext context) { final fs = Theme.of(context).extension()!; return Center( child: Text(message, style: TextStyle(color: fs.ash)), ); } } class _Results extends ConsumerWidget { const _Results({required this.results}); final SearchResponse results; @override Widget build(BuildContext context, WidgetRef ref) { return ListView( children: [ if (results.artists.items.isNotEmpty) ...[ _Header(label: 'Artists', count: results.artists.total), SizedBox( height: 168, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 8), itemCount: results.artists.items.length, itemBuilder: (ctx, i) { final a = results.artists.items[i]; return ArtistCard( artist: a, onTap: () => ctx.push('/artists/${a.id}'), ); }, ), ), ], if (results.albums.items.isNotEmpty) ...[ _Header(label: 'Albums', count: results.albums.total), SizedBox( height: 200, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 8), itemCount: results.albums.items.length, itemBuilder: (ctx, i) { final a = results.albums.items[i]; return AlbumCard( album: a, onTap: () => ctx.push('/albums/${a.id}'), ); }, ), ), ], if (results.tracks.items.isNotEmpty) ...[ _Header(label: 'Tracks', count: results.tracks.total), ...results.tracks.items.asMap().entries.map((e) { final i = e.key; final t = e.value; return TrackRow( track: t, onTap: () => ref .read(playerActionsProvider) .playTracks(results.tracks.items, initialIndex: i), ); }), ], const SizedBox(height: 96), ], ); } } class _Header extends StatelessWidget { const _Header({required this.label, required this.count}); final String label; final int count; @override Widget build(BuildContext context) { final fs = Theme.of(context).extension()!; return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Row( children: [ Text( label, style: TextStyle( color: fs.parchment, fontSize: 18, fontWeight: FontWeight.w500, ), ), const SizedBox(width: 8), Text('$count', style: TextStyle(color: fs.ash, fontSize: 13)), ], ), ); } }