fix(web): Zurück-Knopf tat oft nichts + Besitzer geteilter Playlists (2.6)

navigate() ließ den Stack-Eintrag weg, wenn sich die Ansicht nicht
geändert hatte, rief history.pushState aber trotzdem. Zweimal auf
denselben Tab geklickt = zwei Browser-Einträge auf dieselbe Ansicht,
jeder davon ein Zurück-Druck ohne sichtbare Wirkung. Im Test: 3x "Alben"
-> 3 Browser-Einträge bei 1 Stack-Eintrag, danach bewegten 4 Zurück-
Drücke die Ansicht einmal. Jetzt pusht nur eine wirklich neue Ansicht,
sonst replaceState. Vorwärts mitgetestet, unverändert.

Playlists: Navidrome liefert owner mit, das Web nutzte es nie. Übersicht
und Detail zeigen "· von <Name>", nur bei fremden Listen. Android zeigte
ihn bisher bei jeder Liste und unbeschriftet; beide Plattformen jetzt
gleich, Bibliotheks-Übersicht ebenfalls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-12 14:12:07 +02:00
1 parent ea448e109f
commit 568bb5002c
8 files changed
+85 -14

No files matched your search

@@ -327,9 +327,11 @@ fun LibraryScreen(
modifier = Modifier.fillMaxSize(),
) {
items(state.playlists, key = { it.id }) { pl ->
// Besitzer nur bei fremden (geteilten) Listen — bei den eigenen waere er Rauschen.
val von = pl.owner?.takeIf { it.isNotBlank() && it != state.currentUser }
PlaylistCard(
name = pl.name,
subtitle = "${pl.songCount} Titel",
subtitle = "${pl.songCount} Titel" + (von?.let { " · von $it" } ?: ""),
coverUrl = coverUrl(pl.coverArt),
downloaded = pl.id in downloadedPlaylistIds,
onClick = { onPlaylistClick(pl) },
@@ -174,9 +174,11 @@ fun PlaylistDetailScreen(
fontSize = 26.sp, color = Color.White,
maxLines = 2, overflow = TextOverflow.Ellipsis,
)
// Der Besitzer stand hier bisher bei JEDER Liste, auch bei den eigenen, und ohne
// Beschriftung — bei einer geteilten Liste ist genau das die interessante Angabe.
val meta = listOfNotNull(
pl.owner?.takeIf { it.isNotBlank() },
if (pl.songCount > 0) "${pl.songCount} Songs" else null,
pl.owner?.takeIf { it.isNotBlank() && it != state.currentUser }?.let { "von $it" },
).joinToString(" · ")
if (meta.isNotBlank()) {
Spacer(Modifier.height(4.dp))
@@ -11,6 +11,7 @@ import com.novasonic.app.api.model.Playlist
import com.novasonic.app.app
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@@ -25,6 +26,8 @@ data class LibraryUiState(
val albumSort: AlbumSort = AlbumSort.NEWEST,
val artistIndexes: List<ArtistIndex> = emptyList(),
val playlists: List<Playlist> = emptyList(),
/** Angemeldeter Nutzer — der Besitzer wird nur bei FREMDEN Playlists angezeigt. */
val currentUser: String = "",
val isLoading: Boolean = false,
val error: String? = null,
)
@@ -42,7 +45,13 @@ class LibraryViewModel(app: Application) : AndroidViewModel(app) {
val albumsGridState = LazyGridState()
val artistsListState = LazyListState()
init { loadPlaylists() }
init {
loadPlaylists()
viewModelScope.launch {
val u = runCatching { repo.credentialsFlow().first().username }.getOrDefault("")
_state.value = _state.value.copy(currentUser = u)
}
}
fun setAlbumSort(sort: AlbumSort) {
if (sort == _state.value.albumSort) return
@@ -11,6 +11,7 @@ import com.novasonic.app.offline.DownloadState
import com.novasonic.app.offline.GroupType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -22,6 +23,8 @@ data class PlaylistUiState(
val isSaving: Boolean = false,
val deleted: Boolean = false,
val error: String? = null,
/** Angemeldeter Nutzer — der Besitzer wird nur bei FREMDEN Playlists angezeigt. */
val currentUser: String = "",
)
class PlaylistViewModel(app: Application, savedState: SavedStateHandle) : AndroidViewModel(app) {
@@ -42,7 +45,13 @@ class PlaylistViewModel(app: Application, savedState: SavedStateHandle) : Androi
.map { tracks -> tracks.any { it.state == DownloadState.QUEUED || it.state == DownloadState.DOWNLOADING } }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
init { load() }
init {
load()
viewModelScope.launch {
val u = runCatching { repo.credentialsFlow().first().username }.getOrDefault("")
_state.value = _state.value.copy(currentUser = u)
}
}
fun load() {
val id = playlistId