refactor(server): remove bootstrap admin path #34

Merged
bvandeusen merged 262 commits from dev into main 2026-05-08 22:19:41 -04:00
3 changed files with 32 additions and 11 deletions
Showing only changes of commit d99d317563 - Show all commits
@@ -73,15 +73,16 @@ class ArtistDetailViewModel @Inject constructor(
} }
/** /**
* Pull every track across the artist's albums and start playing. * Pull every track across the artist's albums, shuffle, and start
* Network round-trip per click; fine because the user explicitly * playing. Mirrors Flutter's artist Play button — pressing Play on
* tapped Play and a cache lookup wouldn't be complete anyway * an artist is implicitly "shuffle this artist", not "play album by
* until per-album hydration finishes. * album in tracklist order". Network round-trip per click; fine
* because the user explicitly tapped Play.
*/ */
fun playArtist() { fun playArtist() {
viewModelScope.launch { viewModelScope.launch {
try { try {
val tracks = repository.fetchArtistTracks(artistId) val tracks = repository.fetchArtistTracks(artistId).shuffled()
if (tracks.isNotEmpty()) { if (tracks.isNotEmpty()) {
player.setQueue( player.setQueue(
tracks = tracks, tracks = tracks,
@@ -52,7 +52,6 @@ import com.fabledsword.minstrel.player.RepeatMode
import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.AlbumDetail
import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.nav.ArtistDetail
import com.fabledsword.minstrel.nav.Queue 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.TrackActionsButton
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens 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 PLAY_PAUSE_ICON_DP = 56
private const val MS_PER_SECOND = 1000L private const val MS_PER_SECOND = 1000L
private const val SECONDS_PER_MINUTE = 60L private const val SECONDS_PER_MINUTE = 60L
private const val POP_GRACE_MS = 500L
/** /**
* Full-screen player. Shows the cover (placeholder until AlbumRef join * Full-screen player. Shows the cover (placeholder until AlbumRef join
@@ -85,10 +85,17 @@ fun NowPlayingScreen(
} }
val track = state.currentTrack val track = state.currentTrack
if (track == null) { if (track == null) {
EmptyState( // Session torn down (queue finished + auto-stop, or user cleared
title = "Nothing playing", // the queue from elsewhere). Pop back to whichever shell screen
body = "Pick a track from the library to start.", // 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 return
} }
Scaffold( Scaffold(
@@ -76,9 +76,22 @@ class SearchViewModel @Inject constructor(
setQuery("") 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) { fun playTrack(track: TrackRef) {
if (track.streamUrl.isEmpty()) return 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) { private suspend fun runSearch(q: String) {