This commit is contained in:
Forrest 2026-08-04 16:08:51 -04:00
parent d558aba246
commit 3bd713d667
17 changed files with 1566 additions and 132 deletions

View file

@ -293,11 +293,16 @@ class _QueuePanelState extends ConsumerState<_QueuePanel> {
title: 'Queue',
trailing: '(${state.queue.length})',
padding: const EdgeInsets.symmetric(vertical: TimbreSpacing.md),
child: ListView.builder(
controller: _controller,
child: ReorderableListView.builder(
scrollController: _controller,
padding: EdgeInsets.zero,
itemExtent: _rowExtent,
itemCount: state.queue.length,
// onReorderItem already reports newIndex as the post-removal target
// index — exactly the convention reorderQueue (and just_audio's
// moveAudioSource) expects — so no off-by-one adjustment is needed.
onReorderItem: (oldIndex, newIndex) =>
ref.read(playbackProvider.notifier).reorderQueue(oldIndex, newIndex),
itemBuilder: (context, i) {
final song = state.queue[i];
final current = state.currentIndex;
@ -307,6 +312,11 @@ class _QueuePanelState extends ConsumerState<_QueuePanel> {
? accent
: (isPast ? TimbreColors.dimmed : TimbreColors.foreground);
return InkWell(
// Position-based key: the same Song instance can legitimately sit in
// the queue twice (e.g. added twice), so an identity key would
// collide and ReorderableListView requires unique keys. These rows
// are stateless, so keying by index leaks no state.
key: ValueKey(i),
onTap: () => ref.read(playbackProvider.notifier).jumpTo(i),
child: Padding(
padding: const EdgeInsets.symmetric(