import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../models/artist.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 ArtistCard extends ConsumerWidget { const ArtistCard({ required this.artist, required this.onTap, this.width = 140, super.key, }); final ArtistRef artist; final VoidCallback onTap; /// Outer width of the card. Avatar is a circle of width-16 (8dp /// horizontal padding either side). Default suits horizontal lists; /// grids should pass the cell width so the avatar shrinks /// proportionally and stays a true circle. final double width; @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, children: [ // Stack: circular avatar + overlaid play button at bottom-right. // Avatar size derives from the [width] parameter so the // Library Artists 3-column grid can pass its cell width // (~109dp on typical phones) and the avatar stays a // perfect circle. Previous hardcoded 124×124 got squeezed // horizontally in the grid (cell narrower than 124+16 // padding) but kept its 124dp height — ClipOval produced // a visible vertical ellipse. Mirrors AlbumCard's width- // parameter convention. Stack( children: [ ClipOval( child: Container( width: coverSize, height: coverSize, color: fs.slate, child: artist.coverUrl.isEmpty ? SvgPicture.asset('assets/svg/album-fallback.svg', fit: BoxFit.cover) : ServerImage(url: artist.coverUrl, fit: BoxFit.cover), ), ), Positioned( bottom: 4, right: 4, child: PlayCircleButton( onPressed: () => _playArtistShuffle(ref), ), ), ], ), const SizedBox(height: 8), Text( artist.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: fs.parchment, fontSize: 14), ), ]), ), ), ), ); } /// Fetches the artist's tracks via /api/artists/{id}/tracks, shuffles /// them (Fisher-Yates, default Random), and plays from index 0. /// Matches the web ArtistCard's `playQueue(shuffle(tracks), 0)`. Future _playArtistShuffle(WidgetRef ref) async { final api = await ref.read(libraryApiProvider.future); final tracks = await api.getArtistTracks(artist.id); if (tracks.isEmpty) return; final shuffled = List.of(tracks); shuffled.shuffle(Random()); await ref .read(playerActionsProvider) .playTracks(shuffled, initialIndex: 0); } }