Skip to content

Replace radio station popup menu with a full action sheet#194

Merged
phanan merged 3 commits into
masterfrom
feat/radio-station-action-sheet
May 2, 2026
Merged

Replace radio station popup menu with a full action sheet#194
phanan merged 3 commits into
masterfrom
feat/radio-station-action-sheet

Conversation

@phanan
Copy link
Copy Markdown
Member

@phanan phanan commented May 2, 2026

Summary

Mirrors the album/artist action sheet pattern (#190 / #191) for radio stations. Long-pressing a radio station card or row now opens an Apple-Music-style sheet with:

  • Thumbnail, name, optional description.
  • Quick row: Favorite (gated on koel >= 7.11.0) | Play/Stop. The Play/Stop button is reactive on `RadioPlayerProvider` — it shows Stop while this station is the current loading/playing one, otherwise Play.
  • List rows: Edit (when `canEdit`) | Delete (when `canDelete`). Delete is destructive (red) and asks for confirmation.

Adds the optimistic favorite toggle on `RadioStation` + `RadioStationProvider` (POSTs `favorites/toggle` with `type: 'radio-station'`), and adds a `destructive` flag to `PlayableActionButton` so the Delete row can render in red.

The old `radio_station_actions_menu.dart` is gone. The `confirmDeleteRadioStation` / `deleteRadioStationWithFeedback` helpers move into a small `radio_station_actions.dart` so the swipe-to-delete in the list keeps using them.

Test plan

  • `flutter test` — all 310 tests pass, including new `radio_station_action_sheet_test.dart` (16 tests covering structure, Play/Stop reactivity on a mocked `RadioPlayerProvider`, and action delegation).
  • Long-press a radio station card and a row on a fresh-master koel; exercise Favorite, Play, Stop, Edit, Delete.
  • Long-press while another station is currently playing — verify the action sheet for that other station shows Stop, this one still shows Play.
  • Long-press on koel < 7.11.0: Favorite quick action is hidden.
  • Long-press on a station that's read-only: Edit and Delete rows are hidden.

Summary by CodeRabbit

  • New Features

    • Mark/unmark stations as favorites with immediate UI feedback and persisted sync.
    • Frosted-glass action sheet with station thumbnail offering Play/Stop, Favorite, Edit, and Delete actions; Delete shown as destructive (red).
    • Editing a live station can restart playback or refresh media metadata when appropriate.
  • Refactor

    • Replaced legacy long-press menu with the unified action sheet and simplified tap/long-press interactions.
  • Tests

    • Added tests for favorites, the action sheet UI/behavior, and edit-related player interactions.

Mirrors the album/artist action sheet pattern. The long-press on
radio station cards and rows now opens an Apple-Music-style sheet
with thumbnail, name, optional description, a Favorite + Play/Stop
quick-action row, and Edit / Delete list rows (gated on
canEdit / canDelete).

The Play/Stop button is reactive: it shows Stop while this station
is the current loading or playing one, otherwise Play. Tapping
Play kicks the radio over to this station; Stop stops radio mode.

Adds the koel >= 7.11.0-gated favorite toggle on RadioStation +
RadioStationProvider, and adds a destructive flag to
PlayableActionButton for the red Delete row. The
confirmDeleteRadioStation / deleteRadioStationWithFeedback helpers
move from the deleted radio_station_actions_menu.dart into a small
radio_station_actions.dart so the swipe-to-delete in the list
keeps using them.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d5325ea-b31e-4a3d-a3a1-ba94891e9809

📥 Commits

Reviewing files that changed from the base of the PR and between 50de026 and 2a9a4da.

📒 Files selected for processing (1)
  • lib/ui/screens/radio_station_action_sheet.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/ui/screens/radio_station_action_sheet.dart

📝 Walkthrough

Walkthrough

Replaces the position-based long-press context menu with a bottom-sheet RadioStationActionSheet, adds a favorite field to RadioStation with optimistic toggleFavorite in the provider, introduces destructive styling for PlayableActionButton, moves/delete helpers, updates gestures/exports, and adds/updates tests and mocks.

Changes

Radio Station Favorite & Action Sheet Refactor

Layer / File(s) Summary
Data Shape
lib/models/radio_station.dart
Adds bool favorite (default false); fromJson reads json['favorite'] == true; RadioStation.fake(...) accepts favorite.
Provider Logic
lib/providers/radio_station_provider.dart
Adds Future<void> toggleFavorite(RadioStation station) with optimistic flip, notifyListeners(), persistence via post('favorites/toggle', ...), and rollback + rethrow on error.
Core UI Component Change
lib/ui/screens/playable_action_sheet.dart
PlayableActionButton gains destructive flag; when destructive && enabled it applies the system red color to icon/text and adjusts label color when disabled.
New Screen / Wiring
lib/ui/screens/radio_station_action_sheet.dart, lib/ui/screens/screens.dart
Adds RadioStationActionSheet widget and showRadioStationActionSheet(...); re-exports via screens.dart.
UI Helpers Extracted
lib/ui/widgets/radio_station_actions.dart
Adds confirmDeleteRadioStation and deleteRadioStationWithFeedback (delete calls provider and shows overlay).
Removal of Old Menu
lib/ui/widgets/radio_station_actions_menu.dart
Deletes previous position-based showRadioStationActionsMenu, and its previously collocated confirm/delete helpers (file removed).
Widget Integration / Gesture Simplification
lib/ui/widgets/radio_station_card.dart, lib/ui/screens/radio_stations.dart
Removes stored tap position, simplifies gesture callbacks, and replaces long-press handler with showRadioStationActionSheet(...).
Exports Barrel Update
lib/ui/widgets/widgets.dart
Replaces export of radio_station_actions_menu.dart with radio_station_actions.dart.
Player Provider Addition
lib/providers/radio_player_provider.dart
Adds void refreshMediaItem() to re-emit current station MediaItem with latest stream title.
Edit Dialog Syncing
lib/ui/screens/edit_radio_station_sheet.dart
After update, if edited station is currently playing: restart playback when URL changed, or call refreshMediaItem() when only name changed.
Tests & Mocks
test/models/radio_station_test.dart, test/ui/screens/*.dart, test/ui/screens/*.mocks.dart, test/ui/widgets/radio_station_card_test.dart
Adds tests verifying favorite JSON behavior; adds widget tests for RadioStationActionSheet (UI structure, play/stop, favorite toggle delegation); updates generated mocks with toggleFavorite and refreshMediaItem; removes old long-press context-menu test group from card tests.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sheet as RadioStationActionSheet
    participant StationProv as RadioStationProvider
    participant PlayerProv as RadioPlayerProvider
    participant UI as Overlay/UI

    User->>Sheet: Long-press station → open sheet
    Sheet->>User: Render thumbnail and actions

    rect rgba(100,150,200,0.5)
    User->>Sheet: Tap Favorite / Undo Favorite
    Sheet->>StationProv: toggleFavorite(station)
    StationProv->>StationProv: Optimistically flip station.favorite
    StationProv->>User: notifyListeners() (UI updates)
    StationProv->>StationProv: post('favorites/toggle', ...)
    StationProv-->>Sheet: success or error (on error revert and notify)
    end

    rect rgba(100,150,200,0.5)
    User->>Sheet: Tap Play or Stop
    Sheet->>Sheet: Pop sheet (dismiss)
    Sheet->>PlayerProv: play(station) OR stop()
    Sheet->>UI: swallow errors (no crash)
    end

    rect rgba(100,150,200,0.5)
    User->>Sheet: Tap Delete
    Sheet->>User: Show confirm dialog
    User->>Sheet: Confirm
    Sheet->>Sheet: Pop sheet
    Sheet->>StationProv: remove(station) (fire-and-forget)
    Sheet->>UI: show "Station deleted" overlay
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Poem

🐰 I hopped to the sheet with a twitch and a cheer,
A favorite toggle now snug and near,
Play, stop, edit — the menu took flight,
The rabbit applauds this tidier sight,
hop 🎶

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing a popup menu with an action sheet for radio station interactions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/radio-station-action-sheet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 10 minutes and 50 seconds.

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

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

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

150-156: 💤 Low value

color: Colors.white30 on the destructive Delete icon is dead code.

PlayableActionButton replaces the icon entirely with a new Icon(icon.icon, color: destructiveColor) when destructive: true && enabled: true (see lib/ui/screens/playable_action_sheet.dart), so Colors.white30 is never rendered. Omitting the color argument makes the intent clearer and aligns with how non-destructive icons are already passed in the Edit button (line 137).

♻️ Proposed cleanup
-                        icon: const Icon(
-                          CupertinoIcons.trash,
-                          color: Colors.white30,
-                        ),
+                        icon: const Icon(CupertinoIcons.trash),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ui/screens/radio_station_action_sheet.dart` around lines 150 - 156, The
Delete button passes a colored Icon that is never used because
PlayableActionButton replaces the icon when destructive is true; remove the
color argument from the Icon passed into PlayableActionButton (i.e., change the
Delete button's Icon constructor so it doesn't set color) so the
destructiveColor path in PlayableActionButton is used, matching how the Edit
button supplies its icon.
🤖 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/radio_station_action_sheet.dart`:
- Around line 112-122: The onTap handler discards the Future returned by
radioPlayer.stop() which can produce an unhandled async error; update the onTap
closure so that radioPlayer.stop() is invoked with a catchError handler
(matching how radioPlayer.play(station).catchError((_) {}) and toggleFavorite()
are handled) after Navigator.pop(context) to swallow errors safely; locate the
onTap block that calls Navigator.pop(context) and replace the bare
radioPlayer.stop() call with a call that appends .catchError((_) {}) to prevent
unhandled exceptions.

---

Nitpick comments:
In `@lib/ui/screens/radio_station_action_sheet.dart`:
- Around line 150-156: The Delete button passes a colored Icon that is never
used because PlayableActionButton replaces the icon when destructive is true;
remove the color argument from the Icon passed into PlayableActionButton (i.e.,
change the Delete button's Icon constructor so it doesn't set color) so the
destructiveColor path in PlayableActionButton is used, matching how the Edit
button supplies its icon.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24cd01d3-cd04-486d-8977-371b9c26c263

📥 Commits

Reviewing files that changed from the base of the PR and between 2c380f0 and f054f6f.

📒 Files selected for processing (14)
  • lib/models/radio_station.dart
  • lib/providers/radio_station_provider.dart
  • lib/ui/screens/playable_action_sheet.dart
  • lib/ui/screens/radio_station_action_sheet.dart
  • lib/ui/screens/radio_stations.dart
  • lib/ui/screens/screens.dart
  • lib/ui/widgets/radio_station_actions.dart
  • lib/ui/widgets/radio_station_actions_menu.dart
  • lib/ui/widgets/radio_station_card.dart
  • lib/ui/widgets/widgets.dart
  • test/models/radio_station_test.dart
  • test/ui/screens/radio_station_action_sheet_test.dart
  • test/ui/screens/radio_station_action_sheet_test.mocks.dart
  • test/ui/widgets/radio_station_card_test.dart
💤 Files with no reviewable changes (2)
  • test/ui/widgets/radio_station_card_test.dart
  • lib/ui/widgets/radio_station_actions_menu.dart

Comment thread lib/ui/screens/radio_station_action_sheet.dart
phanan added 2 commits May 2, 2026 12:22
Editing the URL of the station that's currently on air didn't change
what the listener heard: setUrl was only called at play time on a
proxy URL keyed by id, and the player kept pulling bytes through the
existing connection. Editing only the name didn't refresh the OS
media-session metadata either, so the lock screen / notification
kept showing the old name until the next now-playing poll happened
to push a new streamTitle.

Add RadioPlayerProvider.refreshMediaItem(). The edit sheet captures
the old name + URL, and when the edited station is the current one,
restarts the stream on URL change, otherwise refreshes the media
item on name change.
@phanan phanan merged commit a9baf5f into master May 2, 2026
2 checks passed
@phanan phanan deleted the feat/radio-station-action-sheet branch May 2, 2026 10:37
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