Skip to content

Add swipe-right-to-queue across all list screens#129

Merged
phanan merged 1 commit into
masterfrom
fix/always-allow-swipe-to-queue
Mar 23, 2026
Merged

Add swipe-right-to-queue across all list screens#129
phanan merged 1 commit into
masterfrom
fix/always-allow-swipe-to-queue

Conversation

@phanan
Copy link
Copy Markdown
Member

@phanan phanan commented Mar 23, 2026

Summary

  • Extract reusable SwipeToQueueDismissible widget with fetchSongs callback, try/catch error handling, and mounted guards before context usage
  • SliverPlayableList now always wraps rows in Dismissible — swipe right to queue is available even without swipe-to-delete
  • Album rows: swipe right fetches album songs and queues them
  • Artist rows: swipe right fetches artist songs and queues them
  • Playlist rows: swipe right background changed from AppColors.highlight to Colors.green
  • Replace LucideIcons.listPlus with CupertinoIcons.text_badge_plus

Test plan

  • All 108 tests pass
  • Swipe right on song rows → queues song (all screens)
  • Swipe right on album rows → queues all album songs
  • Swipe right on artist rows → queues all artist songs
  • Swipe left still works for delete where applicable
  • Error during fetch shows error overlay instead of crashing

@phanan phanan force-pushed the fix/always-allow-swipe-to-queue branch from 689f7a1 to edf1617 Compare March 23, 2026 16:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

Warning

Rate limit exceeded

@phanan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 33 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3ef41a6-4c7c-468e-b25a-c31e1fa2254c

📥 Commits

Reviewing files that changed from the base of the PR and between ac83fff and 00be205.

📒 Files selected for processing (8)
  • lib/ui/screens/albums.dart
  • lib/ui/screens/artists.dart
  • lib/ui/screens/playlists.dart
  • lib/ui/widgets/sliver_playable_list.dart
  • lib/ui/widgets/swipe_to_queue_dismissible.dart
  • lib/ui/widgets/widgets.dart
  • test/ui/widgets/sliver_playable_list_test.dart
  • test/ui/widgets/swipe_to_queue_dismissible_test.dart
📝 Walkthrough

Walkthrough

Sliver list now always builds a Dismissible (removed lucide_icons), with dismiss controlled by canDismiss. Swiping albums/artists queues their songs via the shared audioHandler and shows a "Queued" overlay; only end-to-start dismisses trigger actual onDismissed.

Changes

Cohort / File(s) Summary
Sliver playable list
lib/ui/widgets/sliver_playable_list.dart
Removed lucide_icons import and replaced LucideIcons.listPlus with CupertinoIcons.text_badge_plus. Builder always returns a Dismissible; canDismiss (based on onDismissed) controls direction/background. confirmDismiss queues and shows "Queued" overlay for non-endToStart swipes, returning false; only endToStart invokes onDismissed and returns true.
Album list swipe-to-queue
lib/ui/screens/albums.dart
Wrapped AlbumRow in Dismissible with ValueKey(album.id) and startToEnd action. Added package:app/main.dart import. confirmDismiss asynchronously calls PlayableProvider.fetchForAlbum, queues each song via audioHandler.queueToBottom, shows showOverlay(context, caption: 'Queued') when songs found, and returns false to keep the row.
Artist list swipe-to-queue
lib/ui/screens/artists.dart
Wrapped ArtistRow in Dismissible with ValueKey(artist.id) and startToEnd action. Added package:app/main.dart import. confirmDismiss calls PlayableProvider.fetchForArtist, queues returned songs with audioHandler.queueToBottom, shows showOverlay(..., 'Queued') when applicable, and returns false.
Playlist swipe color tweak
lib/ui/screens/playlists.dart
Changed dismissible swipe background color from AppColors.highlight to Colors.green for playlist/folder rows; behavior unchanged.
Tests
test/ui/widgets/sliver_playable_list_test.dart
Added tests verifying SliverPlayableList construction in queue-only mode (no onDismissed), behavior when onDismissed provided, and default listContext value.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI (Dismissible)
  participant Provider as PlayableProvider
  participant Audio as audioHandler
  participant Overlay as Overlay

  UI->>UI: User swipes item (start-to-end)
  UI->>Provider: fetchForAlbum/Artist(id)
  Provider-->>UI: List<Song>
  alt songs returned
    UI->>Audio: queueToBottom(song) [for each song]
    Audio-->>UI: ack
    UI->>Overlay: showOverlay("Queued")
    Overlay-->>UI: shown
  else no songs
    UI-->>UI: no overlay
  end
  UI-->>UI: confirmDismiss returns false (row retained)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through lists both near and far,

I wrapped each row like a little star.
A swipe, a queue, a tiny cheer,
Songs stack up — the playlist near.
🥕🎶

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: enabling swipe-right-to-queue functionality on all playable rows, which is the central objective of this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/always-allow-swipe-to-queue

Comment @coderabbitai help to get the list of available commands and usage tips.

@phanan phanan force-pushed the fix/always-allow-swipe-to-queue branch from edf1617 to b54fbd6 Compare March 23, 2026 16:51
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/ui/widgets/sliver_playable_list.dart (1)

53-56: Nitpick: Padding widget can be fully const.

Since both padding and child are const, the entire Padding widget can be const-constructed for a minor optimization.

♻️ Suggested change
-              child: Padding(
-                padding: const EdgeInsets.only(left: 28),
-                child: const Icon(CupertinoIcons.text_badge_plus),
-              ),
+              child: const Padding(
+                padding: EdgeInsets.only(left: 28),
+                child: Icon(CupertinoIcons.text_badge_plus),
+              ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ui/widgets/sliver_playable_list.dart` around lines 53 - 56, The Padding
widget wrapping the Icon can be const-constructed for a minor optimization:
update the Padding instantiation (inside the SliverPlayableList build where you
have Padding(padding: const EdgeInsets.only(left: 28), child: const Icon(...)))
to use a const constructor (const Padding(...)) so the entire widget subtree
becomes compile-time constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/ui/widgets/sliver_playable_list.dart`:
- Around line 53-56: The Padding widget wrapping the Icon can be
const-constructed for a minor optimization: update the Padding instantiation
(inside the SliverPlayableList build where you have Padding(padding: const
EdgeInsets.only(left: 28), child: const Icon(...))) to use a const constructor
(const Padding(...)) so the entire widget subtree becomes compile-time constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b99118eb-be8e-47f4-936e-416639614750

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae610f and edf1617.

📒 Files selected for processing (1)
  • lib/ui/widgets/sliver_playable_list.dart

@phanan phanan force-pushed the fix/always-allow-swipe-to-queue branch 2 times, most recently from 6b231d9 to ac83fff Compare March 23, 2026 16:58
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
lib/ui/screens/artists.dart (1)

151-178: Consider extracting the swipe-to-queue Dismissible into a reusable widget.

The Dismissible wrapper with the fetch-and-queue logic is nearly identical between albums.dart and artists.dart (and presumably could be used elsewhere). Extracting this into a shared widget (e.g., SwipeToQueueDismissible) would reduce duplication and centralize the error handling fix.

Example signature:

class SwipeToQueueDismissible extends StatelessWidget {
  final Key dismissibleKey;
  final Future<List<Song>> Function() fetchSongs;
  final Widget child;
  // ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ui/screens/artists.dart` around lines 151 - 178, Extract the repeated
swipe-to-queue Dismissible into a reusable widget (e.g.,
SwipeToQueueDismissible) and replace the inline Dismissible in artists.dart and
albums.dart with it; the new widget should accept a Key (dismissibleKey), a
fetchSongs callback (Future<List<Song>> Function()), and a child Widget, perform
the same confirmDismiss logic (call
context.read<PlayableProvider>().fetchForArtist or the passed fetchSongs),
iterate and call audioHandler.queueToBottom for each song, call
showOverlay(context, caption: 'Queued') when songs were queued, and return
false; move all related UI config (direction, background alignment/icon/color)
into the new widget so ArtistRow and AlbumRow are wrapped cleanly by
SwipeToQueueDismissible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/ui/screens/albums.dart`:
- Around line 153-164: Wrap the async work in confirmDismiss (the call to
PlayableProvider.fetchForAlbum and the loop calling audioHandler.queueToBottom)
in a try/catch and handle errors (log or show a user-facing message) so
exceptions don't propagate; after each await (or before calling showOverlay)
verify the widget is still mounted (use the State.mounted flag or bail out)
before using context to call showOverlay, and ensure confirmDismiss returns a
safe boolean in all code paths (e.g., return false on error or unmounted).
Target symbols: confirmDismiss, PlayableProvider.fetchForAlbum,
audioHandler.queueToBottom, showOverlay, and mounted/context checks.

In `@lib/ui/screens/artists.dart`:
- Around line 153-163: In the confirmDismiss async closure, wrap the await calls
to PlayableProvider.fetchForArtist and audioHandler.queueToBottom in a try-catch
to handle thrown exceptions, and after each await sequence check that the widget
is still mounted before using context (e.g., before calling showOverlay or
returning); specifically update the confirmDismiss implementation (the closure
that calls fetchForArtist, iterates songs calling queueToBottom, and calls
showOverlay) to catch and handle errors (log or show an error overlay) and to
guard any context usage with a mounted check so you never use context after the
async operations if the widget was disposed.

---

Nitpick comments:
In `@lib/ui/screens/artists.dart`:
- Around line 151-178: Extract the repeated swipe-to-queue Dismissible into a
reusable widget (e.g., SwipeToQueueDismissible) and replace the inline
Dismissible in artists.dart and albums.dart with it; the new widget should
accept a Key (dismissibleKey), a fetchSongs callback (Future<List<Song>>
Function()), and a child Widget, perform the same confirmDismiss logic (call
context.read<PlayableProvider>().fetchForArtist or the passed fetchSongs),
iterate and call audioHandler.queueToBottom for each song, call
showOverlay(context, caption: 'Queued') when songs were queued, and return
false; move all related UI config (direction, background alignment/icon/color)
into the new widget so ArtistRow and AlbumRow are wrapped cleanly by
SwipeToQueueDismissible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f7b032d-682a-4417-9b62-39139846ccaa

📥 Commits

Reviewing files that changed from the base of the PR and between edf1617 and 6b231d9.

📒 Files selected for processing (4)
  • lib/ui/screens/albums.dart
  • lib/ui/screens/artists.dart
  • lib/ui/screens/playlists.dart
  • lib/ui/widgets/sliver_playable_list.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/ui/screens/playlists.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/ui/widgets/sliver_playable_list.dart

Comment thread lib/ui/screens/albums.dart Outdated
Comment on lines +153 to +164
confirmDismiss: (_) async {
final songs = await context
.read<PlayableProvider>()
.fetchForAlbum(album.id);
if (songs.isNotEmpty) {
for (final song in songs) {
await audioHandler.queueToBottom(song);
}
showOverlay(context, caption: 'Queued');
}
return false;
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling and verify context is still valid after async operations.

The confirmDismiss callback has two concerns:

  1. Missing error handling: If fetchForAlbum or queueToBottom throws, the exception propagates uncaught, which could cause unexpected UI behavior or crashes.

  2. Context after async gap: Using context to show the overlay after awaiting network/queue operations is risky—if the widget unmounts during the fetch, this can throw.

🛡️ Proposed fix with error handling and mounted check
 confirmDismiss: (_) async {
+  try {
     final songs = await context
         .read<PlayableProvider>()
         .fetchForAlbum(album.id);
     if (songs.isNotEmpty) {
       for (final song in songs) {
         await audioHandler.queueToBottom(song);
       }
-      showOverlay(context, caption: 'Queued');
+      if (context.mounted) {
+        showOverlay(context, caption: 'Queued');
+      }
     }
+  } catch (e) {
+    // Optionally log or show error feedback
+  }
   return false;
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
confirmDismiss: (_) async {
final songs = await context
.read<PlayableProvider>()
.fetchForAlbum(album.id);
if (songs.isNotEmpty) {
for (final song in songs) {
await audioHandler.queueToBottom(song);
}
showOverlay(context, caption: 'Queued');
}
return false;
},
confirmDismiss: (_) async {
try {
final songs = await context
.read<PlayableProvider>()
.fetchForAlbum(album.id);
if (songs.isNotEmpty) {
for (final song in songs) {
await audioHandler.queueToBottom(song);
}
if (context.mounted) {
showOverlay(context, caption: 'Queued');
}
}
} catch (e) {
// Optionally log or show error feedback
}
return false;
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ui/screens/albums.dart` around lines 153 - 164, Wrap the async work in
confirmDismiss (the call to PlayableProvider.fetchForAlbum and the loop calling
audioHandler.queueToBottom) in a try/catch and handle errors (log or show a
user-facing message) so exceptions don't propagate; after each await (or before
calling showOverlay) verify the widget is still mounted (use the State.mounted
flag or bail out) before using context to call showOverlay, and ensure
confirmDismiss returns a safe boolean in all code paths (e.g., return false on
error or unmounted). Target symbols: confirmDismiss,
PlayableProvider.fetchForAlbum, audioHandler.queueToBottom, showOverlay, and
mounted/context checks.

Comment thread lib/ui/screens/artists.dart Outdated
Comment on lines +153 to +163
confirmDismiss: (_) async {
final songs = await context
.read<PlayableProvider>()
.fetchForArtist(artist.id);
if (songs.isNotEmpty) {
for (final song in songs) {
await audioHandler.queueToBottom(song);
}
showOverlay(context, caption: 'Queued');
}
return false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same concerns as albums.dart: add error handling and mounted check.

This confirmDismiss implementation has the same issues flagged in albums.dart:

  1. No error handling if fetchForArtist or queueToBottom throws
  2. Using context after async operations without verifying the widget is still mounted
🛡️ Proposed fix
 confirmDismiss: (_) async {
+  try {
     final songs = await context
         .read<PlayableProvider>()
         .fetchForArtist(artist.id);
     if (songs.isNotEmpty) {
       for (final song in songs) {
         await audioHandler.queueToBottom(song);
       }
-      showOverlay(context, caption: 'Queued');
+      if (context.mounted) {
+        showOverlay(context, caption: 'Queued');
+      }
     }
+  } catch (e) {
+    // Optionally log or show error feedback
+  }
   return false;
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ui/screens/artists.dart` around lines 153 - 163, In the confirmDismiss
async closure, wrap the await calls to PlayableProvider.fetchForArtist and
audioHandler.queueToBottom in a try-catch to handle thrown exceptions, and after
each await sequence check that the widget is still mounted before using context
(e.g., before calling showOverlay or returning); specifically update the
confirmDismiss implementation (the closure that calls fetchForArtist, iterates
songs calling queueToBottom, and calls showOverlay) to catch and handle errors
(log or show an error overlay) and to guard any context usage with a mounted
check so you never use context after the async operations if the widget was
disposed.

SliverPlayableList only wrapped rows in Dismissible when onDismissed
was set, making swipe-right-to-queue unavailable on screens without
swipe-to-delete (e.g. artist details, album details). Now all rows
are always wrapped — swipe right to queue is always available.
@phanan phanan force-pushed the fix/always-allow-swipe-to-queue branch from ac83fff to 00be205 Compare March 23, 2026 17:01
@phanan phanan changed the title Always allow swipe right to queue on playable rows Add swipe-right-to-queue across all list screens Mar 23, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/ui/widgets/sliver_playable_list_test.dart`:
- Around line 8-30: Change the unit tests to widget tests that build a
SliverPlayableList with a non-empty playables list (use Song.fake() for test
data) so the Dismissible is actually created; in tests for queue-only mode
(PlayableListContext.other with onDismissed null) pump the widget via
testWidgets, locate the Dismissible inside SliverPlayableList and assert its
Dismissible.direction == DismissDirection.startToEnd; in tests for bidirectional
mode (PlayableListContext.favorites with a non-null onDismissed) assert
Dismissible.direction == DismissDirection.horizontal, then simulate swipes via
WidgetTester.drag: verify that an endToStart swipe triggers the onDismissed
callback and that a startToEnd swipe performs the enqueue behavior (e.g., check
queue state or related side-effect) rather than calling onDismissed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b69c922-99cf-47a1-9919-f49af92b9050

📥 Commits

Reviewing files that changed from the base of the PR and between 6b231d9 and ac83fff.

📒 Files selected for processing (5)
  • lib/ui/screens/albums.dart
  • lib/ui/screens/artists.dart
  • lib/ui/screens/playlists.dart
  • lib/ui/widgets/sliver_playable_list.dart
  • test/ui/widgets/sliver_playable_list_test.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/ui/screens/playlists.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/ui/widgets/sliver_playable_list.dart

Comment on lines +8 to +30
test('accepts onDismissed as null for queue-only mode', () {
// Verify that SliverPlayableList can be constructed without onDismissed
// and still supports swipe-to-queue (startToEnd direction)
final widget = SliverPlayableList(
playables: [],
listContext: PlayableListContext.other,
);

expect(widget.onDismissed, isNull);
expect(widget.playables, isEmpty);
});

test('accepts onDismissed for bidirectional swipe', () {
var dismissed = false;

final widget = SliverPlayableList(
playables: [],
listContext: PlayableListContext.favorites,
onDismissed: (_) => dismissed = true,
);

expect(widget.onDismissed, isNotNull);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify implementation contract in widget code and current test coverage gaps.

# 1) Inspect SliverPlayableList dismissal wiring.
rg -n -C3 'Dismissible|DismissDirection|onDismissed|startToEnd|endToStart' lib/ui/widgets/sliver_playable_list.dart

# 2) Inspect this test file for widget tests vs constructor-only tests.
rg -n -C2 "group\\('SliverPlayableList'|testWidgets\\(|test\\(" test/ui/widgets/sliver_playable_list_test.dart

# 3) Discover existing playable test fixtures/fakes to build non-empty rows.
rg -n -C2 'Fake.*Playable|Mock.*Playable|Playable.*fixture|build.*Playable' test

Repository: koel/player

Length of output: 3552


🏁 Script executed:

#!/bin/bash
# Check for Playable construction patterns and widget test examples

# 1) Search for Playable class definition and constructors
fd -e dart lib -o test | xargs grep -l "class Playable" | head -3

# 2) Look for any factory methods or builders in test files
rg -n "Playable.*=" test/ui/widgets/sliver_playable_list_test.dart

# 3) Check if there are any other widget tests in this directory for reference patterns
fd "sliver_playable_list_test.dart|*_test.dart" test/ui/widgets/ | head -5

# 4) Search for testWidgets usage in the test directory
rg -c "testWidgets" test/ui/ | grep -v ":0$" | head -5

Repository: koel/player

Length of output: 773


🏁 Script executed:

#!/bin/bash
# Check the test file for imports and existing widget test patterns

head -50 test/ui/widgets/sliver_playable_list_test.dart

Repository: koel/player

Length of output: 1232


🏁 Script executed:

#!/bin/bash
# Look at existing widget tests and Playable usage patterns

# Check another widget test file for testWidgets patterns
head -80 test/ui/widgets/song_cache_icon_test.dart

# Check for Playable implementation to understand construction
fd "playable.dart" lib --type f

# Look for Playable mock or fake in test files
rg -n "class.*Playable|Playable()" test/ -A 3 | head -50

Repository: koel/player

Length of output: 6134


🏁 Script executed:

#!/bin/bash
# Check Playable class definition and Song relationship

cat lib/models/playable.dart

# Also check if Song extends Playable
rg "class Song|extends Playable" lib/models/ -A 2

Repository: koel/player

Length of output: 1950


🏁 Script executed:

#!/bin/bash
# Check if Song has a .fake() factory/constructor

rg -n "\.fake\(|factory.*fake" lib/models/song.dart -A 2

# Also look at the actual Dismissible behavior in the widget to understand what would be tested
cat -n lib/ui/widgets/sliver_playable_list.dart | sed -n '24,45p'

Repository: koel/player

Length of output: 1257


Behavioral contract is not tested; only constructor fields are asserted.

These tests use test() rather than testWidgets() and create playables: [], so the Dismissible widget is never built and swiped. This misses regressions in the conditional direction/callback logic:

  • Line 30-32: direction is DismissDirection.horizontal when onDismissed != null, else DismissDirection.startToEnd
  • Line 34-36: callback fires only for endToStart direction; other directions (or queue-only startToEnd) trigger queuing instead

Rewrite as testWidgets() with non-empty playables (use Song.fake() for test data) and verify:

  • queue-only mode: Dismissible.direction == DismissDirection.startToEnd when onDismissed == null
  • bidirectional mode: Dismissible.direction == DismissDirection.horizontal when onDismissed != null
  • callback fires only for endToStart swipe; startToEnd swipe enqueues instead
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/ui/widgets/sliver_playable_list_test.dart` around lines 8 - 30, Change
the unit tests to widget tests that build a SliverPlayableList with a non-empty
playables list (use Song.fake() for test data) so the Dismissible is actually
created; in tests for queue-only mode (PlayableListContext.other with
onDismissed null) pump the widget via testWidgets, locate the Dismissible inside
SliverPlayableList and assert its Dismissible.direction ==
DismissDirection.startToEnd; in tests for bidirectional mode
(PlayableListContext.favorites with a non-null onDismissed) assert
Dismissible.direction == DismissDirection.horizontal, then simulate swipes via
WidgetTester.drag: verify that an endToStart swipe triggers the onDismissed
callback and that a startToEnd swipe performs the enqueue behavior (e.g., check
queue state or related side-effect) rather than calling onDismissed.

@phanan phanan merged commit aa2677c into master Mar 23, 2026
2 checks passed
@phanan phanan deleted the fix/always-allow-swipe-to-queue branch March 23, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant