/// Album Details Page - Mobile Layout library; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/theme/colors.dart'; import '../../providers/album_provider.dart'; import '../../providers/music_provider.dart'; import '../../../domain/entities/track.dart'; import '../../widgets/album/album_widgets.dart'; import '../common/cached_network_image_with_fallback.dart'; class AlbumMobilePage extends ConsumerStatefulWidget { final String albumId; const AlbumMobilePage({ required this.albumId, super.key, }); @override ConsumerState createState() => _AlbumMobilePageState(); } class _AlbumMobilePageState extends ConsumerState { @override void initState() { super.initState(); // Load album data Future.microtask(() { ref.read(albumProvider.notifier).loadAlbum(widget.albumId); }); } @override Widget build(BuildContext context) { final albumState = ref.watch(albumProvider); if (albumState.isLoading && albumState.album == null) { return _buildLoadingState(); } if (albumState.error != null && albumState.album == null) { return _buildErrorState(albumState.error!); } if (albumState.album == null) { return _buildEmptyState(); } return CustomScrollView( slivers: [ // Hero header _buildHeroHeader(albumState.album!), // Action buttons _buildActionButtons(albumState.tracks), // Album info _buildAlbumInfo(albumState), // Tracklist if (albumState.tracks.isNotEmpty) _buildTracklistSection(albumState.tracks), // Bottom spacing const SliverToBoxAdapter( child: SizedBox(height: 100), ), ], ); } Widget _buildLoadingState() { return const Center( child: CircularProgressIndicator(color: AppColors.cyan), ); } Widget _buildErrorState(String error) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 64, color: AppColors.error, ), const SizedBox(height: 16), const Text( 'Something went wrong', style: TextStyle( fontSize: 18, color: AppColors.error, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Text( error, style: const TextStyle( fontSize: 14, color: AppColors.muted, ), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton( onPressed: () { ref.read(albumProvider.notifier).loadAlbum(widget.albumId); }, child: const Text('Retry'), ), ], ), ), ); } Widget _buildEmptyState() { return const Center( child: CircularProgressIndicator(color: AppColors.cyan), ); } Widget _buildHeroHeader(album) { return SliverToBoxAdapter( child: Stack( children: [ // Background gradient Container( height: 400, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.violet.withOpacity(0.3), AppColors.primary, ], ), ), ), // Album art background (blurred) if (album.imageUrl != null) Positioned.fill( child: Opacity( opacity: 0.15, child: Image.network( album.imageUrl!, fit: BoxFit.cover, ), ), ), // Content SizedBox( height: 400, child: SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Back button IconButton( icon: const Icon(Icons.arrow_back, color: AppColors.onBackground), onPressed: () => Navigator.of(context).pop(), ), const Spacer(), // Album art Center( child: Hero( tag: 'album_art_${album.id}', child: Container( decoration: BoxDecoration( boxShadow: AppColors.cyanGlow, borderRadius: BorderRadius.circular(8), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: CachedNetworkImageWithFallback( imageUrl: album.imageUrl, fallbackIcon: Icons.album, progressColor: AppColors.cyan, width: 240, height: 240, fit: BoxFit.cover, ), ), ), ), ), const SizedBox(height: 24), // Album title Text( album.title, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: AppColors.onBackground, ), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), const SizedBox(height: 8), // Artist name and year if (album.artist != null) Text( '${album.artist!.name}${album.releaseDate != null ? ' • ${album.releaseDate!.year}' : ''}', style: const TextStyle( fontSize: 15, color: AppColors.onSurfaceVariant, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), const SizedBox(height: 24), ], ), ), ), ), ], ), ); } Widget _buildActionButtons(List tracks) { return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ // Play All button Expanded( child: SizedBox( height: 48, child: ElevatedButton.icon( onPressed: tracks.isNotEmpty ? () { final albumNotifier = ref.read(albumProvider.notifier); final playerNotifier = ref.read(playerProvider.notifier); albumNotifier.playAll(playerNotifier); } : null, icon: const Icon(Icons.play_arrow, size: 24), label: const Text('Play All', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), style: ElevatedButton.styleFrom( backgroundColor: AppColors.cyan, foregroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), elevation: 0, ), ), ), ), const SizedBox(width: 12), // Shuffle button Container( decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.violet, AppColors.rose], ), boxShadow: AppColors.violetGlow, ), child: IconButton( icon: const Icon(Icons.shuffle, size: 20), color: AppColors.onBackground, onPressed: tracks.isNotEmpty ? () { final albumNotifier = ref.read(albumProvider.notifier); final playerNotifier = ref.read(playerProvider.notifier); albumNotifier.shuffle(playerNotifier); } : null, ), ), ], ), ), ); } Widget _buildAlbumInfo(albumState) { final album = albumState.album!; final tracks = albumState.tracks; return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ if (album.totalTracks > 0) _buildInfoChip('${album.totalTracks} ${album.totalTracks == 1 ? 'track' : 'tracks'}'), if (album.totalTracks > 0 && albumState.totalDuration > 0) const SizedBox(width: 8), if (albumState.totalDuration > 0) _buildInfoChip(albumState.formattedTotalDuration), if (album.genre != null) ...[ const SizedBox(width: 8), _buildInfoChip(album.genre!, isGenre: true), ], ], ), ), ); } Widget _buildInfoChip(String text, {bool isGenre = false}) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: isGenre ? AppColors.violet.withOpacity(0.2) : AppColors.surfaceVariant, borderRadius: BorderRadius.circular(16), border: Border.all( color: isGenre ? AppColors.violet.withOpacity(0.5) : AppColors.cyan.withOpacity(0.2), ), ), child: Text( text, style: TextStyle( color: isGenre ? AppColors.violet : AppColors.onSurfaceVariant, fontSize: 12, fontWeight: FontWeight.w500, ), ), ); } Widget _buildTracklistSection(List tracks) { return SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.fromLTRB(16, 24, 16, 12), child: Text( 'Tracklist', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: AppColors.onBackground, ), ), ), ...tracks.asMap().entries.map((entry) { final index = entry.key; final track = entry.value; return AlbumTrackTile( track: track, index: index, onTap: () => _playTrack(track, index), ); }), const SizedBox(height: 24), ], ), ); } void _playTrack(Track track, int index) { final albumNotifier = ref.read(albumProvider.notifier); final playerNotifier = ref.read(playerProvider.notifier); albumNotifier.playTrack(playerNotifier, track); } }