Files
minstrel/flutter_client/lib/playlists/widgets/playlist_card.dart
T
bvandeusen f65650fb6d fix(flutter): player bar layout, system playlist tap, banner height
Player bar: drop the volume slider — phone hardware controls volume.
Right cluster collapses to a single row of shuffle/repeat/queue.

Playlist card: GestureDetector defers to children by default, so taps
over the cover image area didn't always register. Add HitTestBehavior.opaque
so the whole card surface is tappable.

Update banner: was 3-4× the height of its text — IconButton's default
48dp tap target plus generous vertical padding dominated. Tighten
padding (8→4), shrink dismiss button to 32×32 with iconSize 16, slim
the action button to 28dp height with shrinkWrap tap target.

Home screen: ClampingScrollPhysics + 140dp bottom padding (already
applied in working tree) so scroll stops just above the mini-player.
2026-05-11 07:27:14 -04:00

66 lines
2.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../models/playlist.dart';
import '../../shared/widgets/server_image.dart';
import '../../theme/theme_extension.dart';
/// Mirrors the web PlaylistCard. ~176dp wide, square cover (~144dp),
/// name + optional system-variant badge below. Tap pushes
/// `/playlists/{id}`.
class PlaylistCard extends StatelessWidget {
const PlaylistCard({super.key, required this.playlist});
final Playlist playlist;
@override
Widget build(BuildContext context) {
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
return SizedBox(
width: 176,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => context.push('/playlists/${playlist.id}'),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Container(
width: 144,
height: 144,
color: fs.slate,
child: playlist.coverUrl.isEmpty
? Icon(Icons.queue_music, color: fs.ash, size: 56)
: ServerImage(url: playlist.coverUrl, fit: BoxFit.cover),
),
),
const SizedBox(height: 8),
Text(
playlist.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: fs.parchment, fontSize: 14),
),
if (playlist.isSystem)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: fs.iron,
borderRadius: BorderRadius.circular(4),
),
child: Text(
playlist.systemVariant!.replaceAll('_', ' '),
style: TextStyle(color: fs.accent, fontSize: 11),
),
),
),
]),
),
),
);
}
}