feat(flutter/widgets): CompactTrackCard for dense home Most-played row
Mirrors the web CompactTrackCard — small horizontal cell with cover art (48dp), title, artist. Tap plays from this track in the section. Width 176dp matches web's compact density so 25 tracks per row scroll naturally on phone widths. Cover URL is derived from track.albumId (/api/albums/<id>/cover) since TrackRef itself doesn't carry a cover field — same pattern as web's coverUrl() helper. Used in the next commit's home Most-played section (3 rows × 25); otherwise unused this slice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../models/track.dart';
|
||||
import '../../player/player_provider.dart';
|
||||
import '../../shared/widgets/server_image.dart';
|
||||
import '../../theme/theme_extension.dart';
|
||||
|
||||
/// Small horizontal track cell used by the home Most-played section.
|
||||
/// Mirrors the web CompactTrackCard sizing (~176dp wide, ~56dp tall).
|
||||
/// Tap plays from this track within [sectionTracks] starting at [index].
|
||||
class CompactTrackCard extends ConsumerWidget {
|
||||
const CompactTrackCard({
|
||||
super.key,
|
||||
required this.track,
|
||||
required this.sectionTracks,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
final TrackRef track;
|
||||
|
||||
/// All tracks in the home section so play-from-here can queue them
|
||||
/// in order.
|
||||
final List<TrackRef> sectionTracks;
|
||||
final int index;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
// Cover URL derives from the track's album. Mirrors web
|
||||
// CompactTrackCard which calls coverUrl(track.album_id).
|
||||
final coverUrl = track.albumId.isNotEmpty
|
||||
? '/api/albums/${track.albumId}/cover'
|
||||
: '';
|
||||
return SizedBox(
|
||||
width: 176,
|
||||
child: InkWell(
|
||||
onTap: () => ref
|
||||
.read(playerActionsProvider)
|
||||
.playTracks(sectionTracks, initialIndex: index),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Row(children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: SizedBox(
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: ServerImage(
|
||||
url: coverUrl,
|
||||
fit: BoxFit.cover,
|
||||
fallback: Container(color: fs.slate),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
track.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: fs.parchment, fontSize: 13),
|
||||
),
|
||||
Text(
|
||||
track.artistName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: fs.ash, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:minstrel/library/widgets/compact_track_card.dart';
|
||||
import 'package:minstrel/models/track.dart';
|
||||
import 'package:minstrel/theme/theme_data.dart';
|
||||
|
||||
const _track = TrackRef(
|
||||
id: 't1',
|
||||
title: 'Roygbiv',
|
||||
albumId: 'a1',
|
||||
albumTitle: 'Geogaddi',
|
||||
artistId: 'ar1',
|
||||
artistName: 'Boards of Canada',
|
||||
durationSec: 137,
|
||||
trackNumber: 4,
|
||||
streamUrl: '',
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWidgets('renders title and artist', (tester) async {
|
||||
await tester.pumpWidget(ProviderScope(
|
||||
child: MaterialApp(
|
||||
theme: buildThemeData(),
|
||||
home: Scaffold(
|
||||
body: CompactTrackCard(
|
||||
track: _track,
|
||||
sectionTracks: const [_track],
|
||||
index: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
expect(find.text('Roygbiv'), findsOneWidget);
|
||||
expect(find.text('Boards of Canada'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user