feat(flutter): full player — combined action row above seek
Move shuffle / repeat / queue out from below the play controls and combine with the like + kebab into a single row sitting just above the seek bar. Title row drops back to title-only (truly centered now, no Stack needed since nothing competes for the row's right edge). Layout order is now: art → title → artist → album → [shuffle, repeat, queue, like, kebab] → seek → prev/play/next. The "queue" icon in the top-right of the AppBar stays for now — redundant with the new row but matches what users have already muscle-memoried for opening the queue from any player state.
This commit is contained in:
@@ -113,20 +113,21 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 32),
|
|
||||||
_SeekRow(position: pos, duration: dur, fs: fs, ref: ref),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
_PrimaryControls(
|
|
||||||
fs: fs,
|
|
||||||
ref: ref,
|
|
||||||
isPlaying: isPlaying,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_SecondaryControls(
|
_SecondaryControls(
|
||||||
fs: fs,
|
fs: fs,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
shuffleOn: shuffleOn,
|
shuffleOn: shuffleOn,
|
||||||
repeatMode: repeatMode,
|
repeatMode: repeatMode,
|
||||||
|
media: media,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_SeekRow(position: pos, duration: dur, fs: fs, ref: ref),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_PrimaryControls(
|
||||||
|
fs: fs,
|
||||||
|
ref: ref,
|
||||||
|
isPlaying: isPlaying,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
],
|
],
|
||||||
@@ -219,48 +220,14 @@ class _TitleRow extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Stack: title centered absolutely in the row; like + kebab pinned
|
// Title-only — like + kebab moved into _SecondaryControls above
|
||||||
// to the right edge. Padding on the title equals the actions' width
|
// the seek bar so they share the same row as shuffle/repeat/queue.
|
||||||
// so it stays optically centered without colliding with them.
|
return Text(
|
||||||
return SizedBox(
|
media.title,
|
||||||
height: 32,
|
style: TextStyle(color: fs.parchment, fontSize: 22),
|
||||||
child: Stack(
|
maxLines: 1,
|
||||||
alignment: Alignment.center,
|
overflow: TextOverflow.ellipsis,
|
||||||
children: [
|
textAlign: TextAlign.center,
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 64),
|
|
||||||
child: Text(
|
|
||||||
media.title,
|
|
||||||
style: TextStyle(color: fs.parchment, fontSize: 22),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
LikeButton(kind: LikeKind.track, id: media.id, size: 22),
|
|
||||||
TrackActionsButton(
|
|
||||||
track: TrackRef(
|
|
||||||
id: media.id,
|
|
||||||
title: media.title,
|
|
||||||
albumId: (media.extras?['album_id'] as String?) ?? '',
|
|
||||||
albumTitle: media.album ?? '',
|
|
||||||
artistId: (media.extras?['artist_id'] as String?) ?? '',
|
|
||||||
artistName: media.artist ?? '',
|
|
||||||
durationSec: media.duration?.inSeconds ?? 0,
|
|
||||||
streamUrl: '',
|
|
||||||
),
|
|
||||||
hideQueueActions: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,17 +332,23 @@ class _PrimaryControls extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Action row sitting just above the seek bar. Holds shuffle / repeat
|
||||||
|
/// / queue plus the like + kebab that used to live in the title row,
|
||||||
|
/// so the title can sit truly centered above and this row carries
|
||||||
|
/// every per-track action in one place.
|
||||||
class _SecondaryControls extends StatelessWidget {
|
class _SecondaryControls extends StatelessWidget {
|
||||||
const _SecondaryControls({
|
const _SecondaryControls({
|
||||||
required this.fs,
|
required this.fs,
|
||||||
required this.actions,
|
required this.actions,
|
||||||
required this.shuffleOn,
|
required this.shuffleOn,
|
||||||
required this.repeatMode,
|
required this.repeatMode,
|
||||||
|
required this.media,
|
||||||
});
|
});
|
||||||
final FabledSwordTheme fs;
|
final FabledSwordTheme fs;
|
||||||
final PlayerActions actions;
|
final PlayerActions actions;
|
||||||
final bool shuffleOn;
|
final bool shuffleOn;
|
||||||
final AudioServiceRepeatMode repeatMode;
|
final AudioServiceRepeatMode repeatMode;
|
||||||
|
final MediaItem media;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -411,6 +384,20 @@ class _SecondaryControls extends StatelessWidget {
|
|||||||
icon: Icon(Icons.queue_music, color: fs.ash),
|
icon: Icon(Icons.queue_music, color: fs.ash),
|
||||||
onPressed: () => GoRouter.of(context).push('/queue'),
|
onPressed: () => GoRouter.of(context).push('/queue'),
|
||||||
),
|
),
|
||||||
|
LikeButton(kind: LikeKind.track, id: media.id, size: 22),
|
||||||
|
TrackActionsButton(
|
||||||
|
track: TrackRef(
|
||||||
|
id: media.id,
|
||||||
|
title: media.title,
|
||||||
|
albumId: (media.extras?['album_id'] as String?) ?? '',
|
||||||
|
albumTitle: media.album ?? '',
|
||||||
|
artistId: (media.extras?['artist_id'] as String?) ?? '',
|
||||||
|
artistName: media.artist ?? '',
|
||||||
|
durationSec: media.duration?.inSeconds ?? 0,
|
||||||
|
streamUrl: '',
|
||||||
|
),
|
||||||
|
hideQueueActions: true,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user