import 'dart:math'; 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 '../../core/models.dart'; import '../../data/user_library.dart'; import '../album/album_screen.dart'; import '../../shared/widgets/artwork.dart'; import '../../shared/widgets/catalog_builder.dart'; import '../../shared/widgets/section_header.dart'; import '../../shared/widgets/track_tile.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return CatalogBuilder( builder: (catalog) { final recentIds = ref.watch(recentlyPlayedProvider); final recent = recentIds.map(catalog.trackById).whereType().take(6).toList(); final controller = ref.read(playerControllerProvider); // A different mix each day, the same all day. final now = DateTime.now(); final picks = ([...catalog.tracks]..shuffle(Random(now.year * 1000 + now.month * 40 + now.day))) .take(12) .toList(); return CustomScrollView( slivers: [ SliverSafeArea( bottom: false, sliver: SliverPadding( padding: const EdgeInsets.fromLTRB(18, 12, 18, 24), sliver: SliverList.list( children: [ const _Header(), const SizedBox(height: 16), const _SearchField(), const SizedBox(height: 20), _HeroCard(onPlay: () => controller.playTracks(catalog.tracks, shuffle: true)), if (catalog.albums.isNotEmpty) ...[ const SizedBox(height: 24), SectionHeader('Albums', onSeeAll: () => context.go('/explore')), const SizedBox(height: 8), SizedBox( height: 186, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: catalog.albums.length, separatorBuilder: (_, _) => const SizedBox(width: 12), itemBuilder: (_, i) { final album = catalog.albums[i]; return AlbumCard( album: album, songCount: catalog.byAlbum(album.id).length, onTap: () => context.push('/album/${album.id}'), ); }, ), ), ], const SizedBox(height: 24), SectionHeader('Explore by occasion', onSeeAll: () => context.go('/explore')), const SizedBox(height: 8), SizedBox( height: 116, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: Occasion.values.length, separatorBuilder: (_, _) => const SizedBox(width: 12), itemBuilder: (_, i) => _OccasionTile(occasion: Occasion.values[i]), ), ), const SizedBox(height: 24), const SectionHeader('Popular singers'), const SizedBox(height: 8), SizedBox( height: 112, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: catalog.artists.length, separatorBuilder: (_, _) => const SizedBox(width: 14), itemBuilder: (_, i) { final a = catalog.artists[i]; return InkWell( borderRadius: BorderRadius.circular(12), onTap: () => context.push('/artist/${a.id}'), child: SizedBox( width: 80, child: Column( children: [ SingerAvatar(artist: a), const SizedBox(height: 8), Text(a.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)), ], ), ), ); }, ), ), if (recent.isNotEmpty) ...[ const SizedBox(height: 20), const SectionHeader('Recently played'), const SizedBox(height: 4), for (var i = 0; i < recent.length; i++) TrackTile(track: recent[i], onTap: () => controller.playTracks(recent, index: i)), ], const SizedBox(height: 20), const SectionHeader('Recommended for you'), const SizedBox(height: 8), for (var i = 0; i < picks.length; i++) Padding( padding: const EdgeInsets.only(bottom: 10), child: TrackTile( card: true, track: picks[i], subtitle: '${catalog.albumOf(picks[i])?.title ?? picks[i].occasion.label}' ' · ${formatDuration(picks[i].duration)}', onTap: () => controller.playTracks(picks, index: i), ), ), ], ), ), ), ], ); }, ); } } class _Header extends StatelessWidget { const _Header(); @override Widget build(BuildContext context) { return Row( children: [ ClipOval( child: Container( color: NkColors.cream, child: Image.asset('assets/images/emblem.png', width: 40, height: 40, semanticLabel: 'Nanga Kathai'), ), ), const SizedBox(width: 12), Expanded(child: Text('Nanga Kathai', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800))), IconButton( tooltip: 'Account', onPressed: () => context.push('/profile'), icon: const CircleAvatar( radius: 20, backgroundColor: NkColors.emblemGreen, child: Icon(Icons.person_outline, color: NkColors.cream), ), ), ], ); } } class _SearchField extends StatelessWidget { const _SearchField(); @override Widget build(BuildContext context) { return Material( color: NkColors.surface, shape: const StadiumBorder(side: BorderSide(color: NkColors.outline)), child: InkWell( customBorder: const StadiumBorder(), onTap: () => context.push('/search'), child: const SizedBox( height: 52, child: Row( children: [ SizedBox(width: 18), Icon(Icons.search, color: Color(0xFFC9D6CE)), SizedBox(width: 12), Expanded(child: Text('Search songs, singers, lyrics…', style: TextStyle(color: NkColors.muted, fontSize: 15))), ], ), ), ), ); } } class _HeroCard extends StatelessWidget { const _HeroCard({required this.onPlay}); final VoidCallback onPlay; @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(22), child: SizedBox( height: 200, child: Stack( fit: StackFit.expand, children: [ Image.asset('assets/images/nilgiris_valley.jpg', fit: BoxFit.cover, alignment: const Alignment(0, 0.4)), const PhotoScrim(begin: Alignment.centerRight, end: Alignment.centerLeft, stops: [0, 0.9]), Padding( padding: const EdgeInsets.fromLTRB(18, 22, 96, 18), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Sounds of\nthe Nilgiris', style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: 8), const Text( 'Songs, stories and traditions of the Badaga people', style: TextStyle(color: NkColors.mutedOnPhoto, height: 1.4), ), ], ), ), Positioned( right: 18, bottom: 18, child: IconButton.filled( tooltip: 'Shuffle play all songs', style: IconButton.styleFrom(backgroundColor: NkColors.cream, fixedSize: const Size(56, 56)), onPressed: onPlay, icon: const Icon(Icons.play_arrow_rounded, color: NkColors.ground, size: 30), ), ), ], ), ), ); } } class _OccasionTile extends StatelessWidget { const _OccasionTile({required this.occasion}); final Occasion occasion; @override Widget build(BuildContext context) { return Material( color: occasion.color, borderRadius: BorderRadius.circular(16), child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () => context.push('/occasion/${occasion.id}'), child: SizedBox( width: 116, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(occasion.icon, size: 38, color: NkColors.cream), const SizedBox(height: 10), Text(occasion.label, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15)), ], ), ), ), ); } }