import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../models/album.dart'; import '../../player/player_provider.dart'; import '../../shared/widgets/server_image.dart'; import '../../theme/theme_extension.dart'; import '../library_providers.dart'; import 'play_circle_button.dart'; class AlbumCard extends ConsumerWidget { const AlbumCard({ required this.album, required this.onTap, this.width = 140, this.titleMaxLines = 1, this.showArtist = true, super.key, }); final AlbumRef album; final VoidCallback onTap; /// Outer width of the card. Cover is square at width - 16 (8dp /// horizontal padding either side). Default suits horizontal lists; /// grids should pass the cell width so the card scales down. final double width; /// Lets callers (e.g. the artist detail grid) allow the title to /// wrap to a second line so it isn't truncated to a single /// ellipsized character at narrower widths. final int titleMaxLines; /// Suppress the artist name. Useful on surfaces where the artist is /// already implied by the page header (e.g. artist detail). final bool showArtist; @override Widget build(BuildContext context, WidgetRef ref) { final fs = Theme.of(context).extension()!; final coverSize = width - 16; return SizedBox( width: width, child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Stack: cover image + overlaid play button at bottom-right. Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(6), child: Container( width: coverSize, height: coverSize, color: fs.slate, child: album.coverUrl.isEmpty ? SvgPicture.asset('assets/svg/album-fallback.svg', fit: BoxFit.cover) : ServerImage(url: album.coverUrl, fit: BoxFit.cover), ), ), Positioned( bottom: 6, right: 6, child: PlayCircleButton( onPressed: () => _playAlbum(ref), ), ), ], ), const SizedBox(height: 8), Text( album.title, maxLines: titleMaxLines, overflow: TextOverflow.ellipsis, style: TextStyle(color: fs.parchment, fontSize: 14), ), if (showArtist && album.artistName.isNotEmpty) Text( album.artistName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: fs.ash, fontSize: 12), ), ], ), ), ), ), ); } /// Fetches the album's tracks via /api/albums/{id} and starts playback /// from the first track. Errors are swallowed by the button's outer /// try/finally; callers don't surface them — failed fetches just keep /// the spinner visible until the button is retapped. Future _playAlbum(WidgetRef ref) async { final api = await ref.read(libraryApiProvider.future); final result = await api.getAlbum(album.id); if (result.tracks.isEmpty) return; await ref .read(playerActionsProvider) .playTracks(result.tracks, initialIndex: 0); } }