nkbmusic/lib/features/search/search_screen.dart
metatroncubeswdev 178d2d00ab Nanga Kathai: Flutter app, self-hosted backend and song tools
- Flutter app (Android/iOS): home, explore, albums, search, player with
  equalizer, mini player, sign up/sign in with email codes, profile,
  encrypted offline downloads, About page
- server/: one-command deploy of self-hosted Supabase behind HestiaCP/nginx
  (or Caddy), migrations, backups, email templates
- supabase/migrations: schema, row-level security, profiles
- tools/: song scanner and importer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:39:26 -04:00

122 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../app/theme.dart';
import '../../audio/player_providers.dart';
import '../../shared/widgets/artwork.dart';
import '../../shared/widgets/catalog_builder.dart';
import '../../shared/widgets/mini_player.dart';
import '../../shared/widgets/section_header.dart';
import '../../shared/widgets/track_tile.dart';
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) {
return Scaffold(
bottomNavigationBar: const SafeArea(child: MiniPlayer()),
appBar: AppBar(
backgroundColor: NkColors.ground,
titleSpacing: 0,
title: Padding(
padding: const EdgeInsets.only(right: 16),
child: TextField(
controller: _controller,
autofocus: true,
textInputAction: TextInputAction.search,
onChanged: (v) => setState(() => _query = v),
decoration: InputDecoration(
hintText: 'Songs, singers, Tamil or English',
prefixIcon: const Icon(Icons.search),
contentPadding: EdgeInsets.zero,
suffixIcon: _query.isEmpty
? null
: IconButton(
tooltip: 'Clear',
icon: const Icon(Icons.close),
onPressed: () => setState(() {
_controller.clear();
_query = '';
}),
),
),
),
),
),
body: CatalogBuilder(builder: (catalog) {
if (_query.trim().isEmpty) {
return const EmptyState(
icon: Icons.manage_search,
title: 'Find any Badaga song',
message: 'Type in English letters, Tamil script or the English meaning. Spelling variations are fine.',
);
}
final songs = catalog.searchTracks(_query);
final singers = catalog.searchArtists(_query);
final albums = catalog.searchAlbums(_query);
if (songs.isEmpty && singers.isEmpty && albums.isEmpty) {
return EmptyState(
icon: Icons.search_off,
title: 'No results for "$_query"',
message: 'Try a shorter word or another spelling.',
);
}
final controller = ref.read(playerControllerProvider);
return ListView(
padding: const EdgeInsets.fromLTRB(18, 8, 18, 24),
children: [
if (singers.isNotEmpty) ...[
const SectionHeader('Singers'),
for (final a in singers)
ListTile(
contentPadding: EdgeInsets.zero,
leading: SingerAvatar(artist: a, size: 48),
title: Text(a.name, style: const TextStyle(fontWeight: FontWeight.w700)),
subtitle: Text(a.hatti ?? 'Singer', style: const TextStyle(color: NkColors.muted)),
onTap: () => context.push('/artist/${a.id}'),
),
const SizedBox(height: 12),
],
if (albums.isNotEmpty) ...[
const SectionHeader('Albums'),
for (final a in albums)
ListTile(
contentPadding: EdgeInsets.zero,
leading: ClipRRect(
borderRadius: BorderRadius.circular(6),
child: SizedBox.square(dimension: 48, child: AlbumCover(album: a, size: 48)),
),
title: Text(a.title, style: const TextStyle(fontWeight: FontWeight.w700)),
subtitle: Text('${catalog.byAlbum(a.id).length} songs', style: const TextStyle(color: NkColors.muted)),
onTap: () => context.push('/album/${a.id}'),
),
const SizedBox(height: 12),
],
if (songs.isNotEmpty) ...[
const SectionHeader('Songs'),
for (var i = 0; i < songs.length; i++)
TrackTile(track: songs[i], onTap: () => controller.playTracks(songs, index: i)),
],
],
);
}),
);
}
}