fix(music): observe playlist and track notes so cards update on relay arrival

The playlist header and each PlaylistTrackRow were reading note.event
synchronously, so when a fresh playlist (or a track referenced by the
playlist) arrived from a relay after first composition, the row stayed
on its 'Unknown track' / stale-snapshot placeholder until the parent
recomposed for some other reason.

Switch both to observeNoteEvent<T>, which subscribes to the note's
metadata flow AND drives the EventFinderFilterAssembler — so a relay
delivering the event both updates the local cache and triggers
recomposition on the same call.
This commit is contained in:
Claude
2026-05-27 15:36:30 +00:00
parent 3f3ccd88c8
commit a403f44d8b
@@ -40,6 +40,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -59,7 +60,7 @@ import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -94,10 +95,15 @@ fun RenderMusicPlaylist(
accountViewModel: AccountViewModel,
nav: INav,
) {
val noteEvent = note.event as? MusicPlaylistEvent ?: return
// Observe so the card recomposes when a newer version of this playlist arrives from
// a relay (e.g. tracks added/removed, title changed). observeNoteEvent also drives
// the underlying EventFinderFilterAssembler subscription, so the playlist body itself
// gets fetched from relays if only the address was known.
val noteEvent by observeNoteEvent<MusicPlaylistEvent>(note, accountViewModel)
val event = noteEvent ?: return
MusicPlaylistHeader(
noteEvent = noteEvent,
noteEvent = event,
note = note,
makeItShort = makeItShort,
canPreview = canPreview,
@@ -222,13 +228,6 @@ fun MusicPlaylistHeader(
}
LoadAddressableNote(address, accountViewModel) { trackNote ->
if (trackNote != null) {
// Ask relays for the track event itself (and its
// reactions/replies) so a playlist whose tracks aren't
// already in cache populates instead of sitting on the
// "Loading…" placeholder. The subscription is keyed on
// the AddressableNote and lifecycle-aware, so it stops
// when the playlist scrolls off-screen.
EventFinderFilterAssemblerSubscription(trackNote, accountViewModel)
PlaylistTrackRow(
position = index + 1,
trackNote = trackNote,
@@ -325,7 +324,12 @@ private fun PlaylistTrackRow(
accountViewModel: AccountViewModel,
nav: INav,
) {
val trackEvent = trackNote.event as? MusicTrackEvent
// Observe the track note so each row recomposes when the track event arrives from a
// relay (cards switch from "Unknown track" placeholder to the real title/artist/cover)
// or a newer revision replaces the cached one. observeNoteEvent also drives the
// EventFinderFilterAssembler subscription, so the row pulls the track itself if only
// the address is known.
val trackEvent by observeNoteEvent<MusicTrackEvent>(trackNote, accountViewModel)
val title = trackEvent?.title() ?: stringRes(R.string.music_playlist_unknown_track)
val artist = trackEvent?.artist()
val duration = trackEvent?.duration()