diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt index bd9c0492..86363455 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt @@ -73,15 +73,16 @@ class ArtistDetailViewModel @Inject constructor( } /** - * Pull every track across the artist's albums and start playing. - * Network round-trip per click; fine because the user explicitly - * tapped Play and a cache lookup wouldn't be complete anyway - * until per-album hydration finishes. + * Pull every track across the artist's albums, shuffle, and start + * playing. Mirrors Flutter's artist Play button — pressing Play on + * an artist is implicitly "shuffle this artist", not "play album by + * album in tracklist order". Network round-trip per click; fine + * because the user explicitly tapped Play. */ fun playArtist() { viewModelScope.launch { try { - val tracks = repository.fetchArtistTracks(artistId) + val tracks = repository.fetchArtistTracks(artistId).shuffled() if (tracks.isNotEmpty()) { player.setQueue( tracks = tracks, diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt index 4c3b6a1a..ccd1331d 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/ui/NowPlayingScreen.kt @@ -52,7 +52,6 @@ import com.fabledsword.minstrel.player.RepeatMode import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.nav.Queue -import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel import com.fabledsword.minstrel.theme.FabledSwordFlatTokens @@ -63,6 +62,7 @@ private const val TRANSPORT_ICON_DP = 36 private const val PLAY_PAUSE_ICON_DP = 56 private const val MS_PER_SECOND = 1000L private const val SECONDS_PER_MINUTE = 60L +private const val POP_GRACE_MS = 500L /** * Full-screen player. Shows the cover (placeholder until AlbumRef join @@ -85,10 +85,17 @@ fun NowPlayingScreen( } val track = state.currentTrack if (track == null) { - EmptyState( - title = "Nothing playing", - body = "Pick a track from the library to start.", - ) + // Session torn down (queue finished + auto-stop, or user cleared + // the queue from elsewhere). Pop back to whichever shell screen + // launched NowPlaying rather than stranding the user on an + // EmptyState with no escape. A short delay swallows the + // momentary null during MediaController IPC bind on cold-mount. + LaunchedEffect(Unit) { + kotlinx.coroutines.delay(POP_GRACE_MS) + if (viewModel.uiState.value.currentTrack == null) { + navController.popBackStack() + } + } return } Scaffold( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt index 673c685f..103765ac 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt @@ -76,9 +76,22 @@ class SearchViewModel @Inject constructor( setQuery("") } + /** + * Tapping a search result builds a queue from the full visible + * track-results list starting at the tapped entry, matching Flutter. + * Falls back to single-track playback when the loaded state isn't + * a Success (shouldn't happen in normal flow but keeps the call + * safe). + */ fun playTrack(track: TrackRef) { if (track.streamUrl.isEmpty()) return - player.setQueue(listOf(track), initialIndex = 0, source = "search") + val loaded = internal.value.results as? SearchResultsState.Loaded + val playable = loaded?.response?.tracks + ?.filter { it.streamUrl.isNotEmpty() } + ?.takeIf { it.isNotEmpty() } + ?: listOf(track) + val startIndex = playable.indexOfFirst { it.id == track.id }.coerceAtLeast(0) + player.setQueue(playable, initialIndex = startIndex, source = "search") } private suspend fun runSearch(q: String) {