6.8 KiB
Feature: Replace "Play On" Popups with Player Selector
Overview
Replace the PlayerPickerSheet popup modal with the existing Player Selector (reveal overlay) when users tap "Play on" buttons throughout the app. This provides a consistent, richer player selection experience.
Current State
PlayerPickerSheet Popup
Used in 7+ places:
album_details_screen.dart- Play Album, Play From Here, Play Radioplaylist_details_screen.dart- Play Playlist, Play Radiosearch_screen.dart- Play album/playlist/audiobook/radio from searchpodcast_detail_screen.dart- Play Episodeaudiobook_detail_screen.dart- Play Audiobookqueue_screen.dart- Transfer Queue (special case - keep popup)
Player Selector (Reveal Overlay)
- Location:
lib/widgets/player/player_reveal_overlay.dart - Shows vertical list of all players with artwork, current track, sync status
- Has hints above player list: "Long-press to sync" & "Swipe to adjust volume"
- Triggered by pull-down gesture or device button tap
Proposed Changes
1. Modify PlayerRevealOverlay
File: lib/widgets/player/player_reveal_overlay.dart
Add new optional parameters:
class PlayerRevealOverlay extends StatefulWidget {
final VoidCallback onDismiss;
final double miniPlayerBottom;
final double miniPlayerHeight;
final bool showOnboardingHints;
// NEW: Callback when player is selected for a pending action
final void Function(Player player)? onPlayerSelected;
// NEW: Context hint to display instead of default hints
// e.g., "Select player to play album"
final String? contextHint;
// NEW: Icon for context hint (default: speaker icon)
final IconData? contextHintIcon;
2. Modify Hint Display
File: lib/widgets/player/player_reveal_overlay.dart (around line 386-394)
// Current:
: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildHintRow(Icons.lightbulb_outline, S.of(context)!.holdToSync),
const SizedBox(height: 4),
buildHintRow(Icons.lightbulb_outline, S.of(context)!.swipeToAdjustVolume),
],
),
// New:
: widget.contextHint != null
? buildHintRow(
widget.contextHintIcon ?? Icons.play_circle_outline,
widget.contextHint!,
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildHintRow(Icons.lightbulb_outline, S.of(context)!.holdToSync),
const SizedBox(height: 4),
buildHintRow(Icons.lightbulb_outline, S.of(context)!.swipeToAdjustVolume),
],
),
3. Modify Player Card onTap
File: lib/widgets/player/player_reveal_overlay.dart (around line 437-441 and 490-494)
// Current:
onTap: () {
HapticFeedback.mediumImpact();
maProvider.selectPlayer(data.player);
dismiss();
},
// New:
onTap: () {
HapticFeedback.mediumImpact();
if (widget.onPlayerSelected != null) {
// Execute the pending action with selected player
widget.onPlayerSelected!(data.player);
} else {
// Default behavior: just select the player
maProvider.selectPlayer(data.player);
}
dismiss();
},
4. Add Helper Method to GlobalPlayerOverlay
File: lib/widgets/global_player_overlay.dart
Add a static method to show player selector with pending action:
/// Show player selector for a "Play on" action
///
/// [contextHint] - Message shown instead of default hints
/// [onPlayerSelected] - Called when user taps a player
///
/// Example:
/// ```dart
/// GlobalPlayerOverlay.showPlayerSelectorForAction(
/// contextHint: "Select player to play album",
/// onPlayerSelected: (player) {
/// provider.playAlbum(albumId, playerId: player.playerId);
/// },
/// );
/// ```
static void showPlayerSelectorForAction({
required String contextHint,
required void Function(Player player) onPlayerSelected,
IconData? hintIcon,
}) {
// Implementation: trigger the player reveal overlay with these parameters
// May need to store pending action in state and pass to overlay
}
5. Update Calling Sites
Replace showPlayerPickerSheet() calls with new method:
Example - album_details_screen.dart:
// Current (around line 1305-1316):
showPlayerPickerSheet(
context: context,
title: 'Play Album',
onPlayerSelected: (player) async {
GlobalPlayerOverlay.hidePlayer();
await maProvider.playAlbum(albumId, playerId: player.playerId);
GlobalPlayerOverlay.showPlayer();
},
);
// New:
GlobalPlayerOverlay.showPlayerSelectorForAction(
contextHint: S.of(context)!.selectPlayerToPlayAlbum, // Add localization
onPlayerSelected: (player) async {
await maProvider.playAlbum(albumId, playerId: player.playerId);
},
);
6. Add Localizations
File: lib/l10n/app_en.arb (and other language files)
"selectPlayerToPlay": "Select player to play",
"selectPlayerToPlayAlbum": "Select player to play album",
"selectPlayerToPlayPlaylist": "Select player to play playlist",
"selectPlayerToPlayRadio": "Select player to start radio",
"selectPlayerToPlayEpisode": "Select player to play episode",
"selectPlayerToPlayAudiobook": "Select player to play audiobook"
Files to Modify
lib/widgets/player/player_reveal_overlay.dart- Add parameters, modify hints & onTaplib/widgets/global_player_overlay.dart- Add helper methodlib/screens/album_details_screen.dart- Replace 3 popup callslib/screens/playlist_details_screen.dart- Replace 2 popup callslib/screens/search_screen.dart- Replace ~5 popup callslib/screens/podcast_detail_screen.dart- Replace 1 popup calllib/screens/audiobook_detail_screen.dart- Replace 1 popup calllib/l10n/app_en.arb- Add new stringslib/l10n/app_de.arb,app_fr.arb,app_es.arb- Translations
Exception: Queue Transfer
Keep the popup for queue_screen.dart because:
- Needs to filter out current player (can't transfer to self)
- Needs to filter out certain player types
- Different UX intent (transfer vs play)
Implementation Order
- Modify
PlayerRevealOverlayto accept new parameters - Add helper method to
GlobalPlayerOverlay - Test with one screen (album_details_screen.dart)
- Add localizations
- Migrate remaining screens
- Clean up unused code from PlayerPickerSheet if no longer needed elsewhere
Testing Checklist
- Album details: Play Album button
- Album details: Play From Here (track context menu)
- Album details: Play Radio (track context menu)
- Playlist details: Play Playlist button
- Playlist details: Play Radio (track context menu)
- Search: Play album from results
- Search: Play playlist from results
- Search: Play radio from results
- Podcast detail: Play episode
- Audiobook detail: Play audiobook
- Queue transfer still works with popup
- Long-press to sync still works in player selector
- Dismiss by tapping backdrop works
- Dismiss by swiping down works