c8baaa5329
- TrackRow gains an actions: bool = true param; renders the button after duration. Album detail + Search + History/Liked tabs all consume TrackRow so they pick up the menu transitively. - CompactTrackCard (home Most-played) gets the button at the end of its inner row. - _PlaylistTrackRow in playlist_detail_screen gets the button next to duration; skipped for unavailable rows (no track id). - NowPlayingScreen renders the button next to the title with hideQueueActions: true (Play next / Add to queue suppressed since the menu's track IS the playing one). artistId is left empty since it's not stashed in MediaItem.extras — Go-to-artist will route to /artists/ which is benign for v1; a follow-up could stash it. Closes Tier 1 follow-up #2 (track-level actions menu). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
85 lines
2.8 KiB
Dart
85 lines
2.8 KiB
Dart
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 '../../shared/widgets/track_actions/track_actions_button.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),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
TrackActionsButton(track: track),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|