feat(flutter): wire TrackActionsButton into 4 surfaces

- 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>
This commit is contained in:
2026-05-09 13:43:12 -04:00
parent 36006d703d
commit c8baaa5329
3 changed files with 29 additions and 1 deletions
@@ -4,6 +4,7 @@ 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.
@@ -74,6 +75,7 @@ class CompactTrackCard extends ConsumerWidget {
],
),
),
TrackActionsButton(track: track),
]),
),
),
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../models/track.dart';
import '../shared/widgets/track_actions/track_actions_button.dart';
import '../theme/theme_extension.dart';
import 'player_provider.dart';
@@ -46,7 +48,28 @@ class NowPlayingScreen extends ConsumerWidget {
width: 280, height: 280, color: fs.slate,
),
const SizedBox(height: 24),
Text(media.title, style: TextStyle(color: fs.parchment, fontSize: 22)),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Flexible(
child: Text(
media.title,
style: TextStyle(color: fs.parchment, fontSize: 22),
overflow: TextOverflow.ellipsis,
),
),
TrackActionsButton(
track: TrackRef(
id: media.id,
title: media.title,
albumId: (media.extras?['album_id'] as String?) ?? '',
albumTitle: media.album ?? '',
artistId: '', // not stashed in MediaItem.extras today
artistName: media.artist ?? '',
durationSec: media.duration?.inSeconds ?? 0,
streamUrl: '',
),
hideQueueActions: true,
),
]),
Text(media.artist ?? '', style: TextStyle(color: fs.ash, fontSize: 14)),
const SizedBox(height: 16),
Slider(
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import '../models/playlist.dart';
import '../models/track.dart';
import '../player/player_provider.dart';
import '../shared/widgets/track_actions/track_actions_button.dart';
import '../theme/theme_extension.dart';
import 'playlists_provider.dart';
@@ -184,6 +185,8 @@ class _PlaylistTrackRow extends StatelessWidget {
),
),
Text('$mins:$secs', style: TextStyle(color: fs.ash, fontSize: 12)),
if (row.trackId != null)
TrackActionsButton(track: _toTrackRef(row)),
]),
),
);