/// Artist Details Page - Desktop Layout library; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/theme/colors.dart'; import '../../providers/artist_provider.dart'; import '../../providers/music_provider.dart'; import '../../../domain/entities/track.dart'; import '../../widgets/artist/artist_track_tile.dart'; import '../../widgets/artist/artist_album_card.dart'; class ArtistDesktopPage extends ConsumerStatefulWidget { final String artistId; const ArtistDesktopPage({ required this.artistId, super.key, }); @override ConsumerState createState() => _ArtistDesktopPageState(); } class _ArtistDesktopPageState extends ConsumerState { @override void initState() { super.initState(); // Load artist data Future.microtask(() { ref.read(artistProvider.notifier).loadAllArtistData(widget.artistId); }); } @override Widget build(BuildContext context) { final artistState = ref.watch(artistProvider); if (artistState.isLoading && artistState.artist == null) { return _buildLoadingState(); } if (artistState.error != null && artistState.artist == null) { return _buildErrorState(artistState.error!); } if (artistState.artist == null) { return _buildEmptyState(); } return CustomScrollView( slivers: [ // Hero header _buildHeroHeader(artistState.artist!), // Main content SliverToBoxAdapter( child: _buildMainContent(artistState), ), // 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(artistProvider.notifier).loadAllArtistData(widget.artistId); }, child: const Text('Retry'), ), ], ), ), ); } Widget _buildEmptyState() { return const Center( child: CircularProgressIndicator(color: AppColors.cyan), ); } Widget _buildHeroHeader(artist) { return SliverToBoxAdapter( child: Stack( children: [ // Background gradient Container( height: 350, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.violet.withOpacity(0.2), AppColors.primary, ], ), ), ), // Artist image background if (artist.imageUrl != null) Positioned.fill( child: Opacity( opacity: 0.15, child: Image.network( artist.imageUrl!, fit: BoxFit.cover, ), ), ), // Content SizedBox( height: 350, child: Row( children: [ const SizedBox(width: 48), // Back button Padding( padding: const EdgeInsets.only(top: 24), child: IconButton( icon: const Icon(Icons.arrow_back, color: AppColors.onBackground), onPressed: () => Navigator.of(context).pop(), ), ), const Spacer(), // Artist image and info Expanded( flex: 2, child: Padding( padding: const EdgeInsets.symmetric(vertical: 48), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const Spacer(), // Artist image if (artist.imageUrl != null) ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( artist.imageUrl!, width: 220, height: 220, fit: BoxFit.cover, ), ), const SizedBox(height: 20), // Artist name Text( artist.name, style: const TextStyle( fontSize: 36, fontWeight: FontWeight.bold, color: AppColors.onBackground, letterSpacing: -0.5, ), textAlign: TextAlign.center, ), const SizedBox(height: 12), // Genres if (artist.genres.isNotEmpty) Wrap( spacing: 12, alignment: WrapAlignment.center, children: artist.genres.take(4).map((genre) { return Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), decoration: BoxDecoration( color: AppColors.surfaceVariant, borderRadius: BorderRadius.circular(20), border: Border.all( color: AppColors.cyan.withOpacity(0.3), ), ), child: Text( genre, style: const TextStyle( color: AppColors.cyan, fontSize: 13, fontWeight: FontWeight.w500, ), ), ); }).toList(), ), const Spacer(), ], ), ), ), const Spacer(flex: 3), ], ), ), ], ), ); } Widget _buildMainContent(artistState) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Play All button _buildPlayAllButton(artistState.topTracks), const SizedBox(height: 32), // Two column layout Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Left column - Popular tracks Expanded( child: _buildPopularTracksSection(artistState.topTracks), ), const SizedBox(width: 48), // Right column - Albums and Related Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Albums section if (artistState.albums.isNotEmpty) _buildAlbumsSection(artistState.albums), const SizedBox(height: 32), // Related tracks section if (artistState.relatedTracks.isNotEmpty) _buildRelatedTracksSection(artistState.relatedTracks), ], ), ), ], ), ], ), ); } Widget _buildPlayAllButton(List tracks) { return SizedBox( width: 200, child: ElevatedButton.icon( onPressed: tracks.isNotEmpty ? () { final playerNotifier = ref.read(playerProvider.notifier); playerNotifier.setQueue(tracks, startIndex: 0); playerNotifier.loadTrack(tracks.first); playerNotifier.play(); } : null, icon: const Icon(Icons.play_arrow, size: 28), label: const Text('Play All', style: TextStyle(fontSize: 16)), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: AppColors.cyan, foregroundColor: AppColors.primary, ), ), ); } Widget _buildPopularTracksSection(List tracks) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Popular Tracks', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.onBackground, ), ), const SizedBox(height: 16), Container( decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.cyan.withOpacity(0.1), ), ), child: Column( children: tracks.asMap().entries.map((entry) { final index = entry.key; final track = entry.value; return ArtistTrackTile( track: track, index: index, onTap: () => _playTrack(track), ); }).toList(), ), ), ], ); } Widget _buildAlbumsSection(albums) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Albums', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.onBackground, ), ), const SizedBox(height: 16), // Grid of albums - 2 per row on desktop Column( children: [ for (int i = 0; i < albums.length; i += 2) Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( children: [ Expanded( child: ArtistAlbumCard( album: albums[i], onTap: () { // TODO: Navigate to album details }, ), ), if (i + 1 < albums.length) ...[ const SizedBox(width: 16), Expanded( child: ArtistAlbumCard( album: albums[i + 1], onTap: () { // TODO: Navigate to album details }, ), ), ], ], ), ), ], ), ], ); } Widget _buildRelatedTracksSection(List tracks) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Related Tracks', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.onBackground, ), ), const SizedBox(height: 16), Container( decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.violet.withOpacity(0.1), ), ), child: Column( children: tracks.take(5).toList().asMap().entries.map((entry) { final index = entry.key; final track = entry.value; return ArtistTrackTile( track: track, index: index, onTap: () => _playTrack(track), ); }).toList(), ), ), ], ); } void _playTrack(Track track) { final playerNotifier = ref.read(playerProvider.notifier); final artistState = ref.read(artistProvider); playerNotifier.setQueue(artistState.topTracks, startIndex: 0); playerNotifier.loadTrack(track); playerNotifier.play(); } }