Files
bvandeusen bf0ef5e0c3 fix(flutter): keep artist avatars round in the Library grid
ArtistCard hardcoded its avatar at Container(width: 124, height:
124) inside a ClipOval. In the Library Artists 3-column grid the
cell is narrower than the card's nominal 140dp width — on a typical
phone the cell is ~109dp, the padded inner content area ~93dp. The
parent constrained the Container's width to ~93dp but the explicit
height stayed 124dp, so ClipOval clipped a 93×124 rectangle and
the avatar rendered as a vertical ellipse.

Fix mirrors AlbumCard's pattern: ArtistCard takes an optional
`width` parameter (default 140 for horizontal carousels) and
derives coverSize = width - 16, so the Container is always square.
ArtistsTab now uses LayoutBuilder to compute cell width and passes
it through, same as AlbumsTab. Avatar stays a true circle at any
cell width.

mainAxisExtent on the grid replaces the previous fixed
childAspectRatio so cell height tracks cellW + name line, with
slack matching AlbumsTab's overflow guard.
2026-05-14 08:39:37 -04:00

102 lines
3.6 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<FabledSwordTheme>()!;
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<void> _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);
}
}