mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix(nip29): audit fixes for pinned-message jump and back-fill
- Jump effect: key on the note id alone and always clear the request after one attempt. Previously keyed on (id, items.list), so a message arriving mid-jump cancelled the scroll animation and could leave the request stuck (never cleared when the target wasn't loaded), blocking repeat taps. Now the animation can't be interrupted and a re-tap always re-fires. - Back-fill pinned bodies: add an id-scoped filter for the group's pinnedEventIds on the host relay. A pin can point at a message older than the 200-item timeline window, which the h-scoped timeline never returns — without this the bar shows a blank preview and can't jump. - Re-invalidate the relay-group filter when the pin list changes (keyed on the pin ids only, so roster/metadata churn doesn't re-subscribe), so the new pin ids are actually requested when 39005 arrives.
This commit is contained in:
+5
-5
@@ -194,18 +194,18 @@ fun ChatFeedLoaded(
|
||||
}
|
||||
}
|
||||
|
||||
// External jump request (pinned-message bar). Re-runs when the requested id changes or the loaded
|
||||
// list shifts (so a pin that arrives after the tap still scrolls once loaded). Clears the request
|
||||
// as soon as it lands, so a repeat tap on the same pin fires again.
|
||||
// External jump request (pinned-message bar). Keyed on the id alone, so a message arriving mid-jump
|
||||
// can't cancel the scroll animation or restart the effect. Always clears the request after one
|
||||
// attempt — even when the target isn't loaded — so it never sticks and a repeat tap fires again.
|
||||
val jumpId = jumpToNoteId?.value
|
||||
LaunchedEffect(jumpId, items.list) {
|
||||
LaunchedEffect(jumpId) {
|
||||
if (jumpId != null) {
|
||||
val index = items.list.indexOfFirst { it.idHex == jumpId }
|
||||
if (index >= 0) {
|
||||
listState.animateScrollToItem(index)
|
||||
highlightedNoteId.value = jumpId
|
||||
onJumpHandled()
|
||||
}
|
||||
onJumpHandled()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@@ -58,4 +59,18 @@ fun ChannelFilterAssemblerSubscription(
|
||||
dataSource.invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
// Relay groups: when the kind-39005 pin list changes, re-invalidate so the id-based back-fill
|
||||
// for pinned message bodies (see filterMetadataToRelayGroup) picks up the new ids. Keyed on the
|
||||
// pin list alone, so unrelated roster/metadata churn doesn't force a re-subscribe.
|
||||
if (channel is RelayGroupChannel) {
|
||||
val metadataState by channel
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
.collectAsStateWithLifecycle()
|
||||
val pinnedIds = (metadataState.channel as? RelayGroupChannel)?.pinnedEventIds ?: channel.pinnedEventIds
|
||||
LaunchedEffect(pinnedIds) {
|
||||
dataSource.invalidateFilters()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-12
@@ -49,15 +49,32 @@ private val RELAY_GROUP_METADATA_KINDS =
|
||||
fun filterMetadataToRelayGroup(
|
||||
channel: RelayGroupChannel,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter> =
|
||||
channel.relays().toSet().map {
|
||||
RelayBasedFilter(
|
||||
relay = it,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = RELAY_GROUP_METADATA_KINDS,
|
||||
tags = mapOf("d" to listOf(channel.groupId.id)),
|
||||
since = since?.get(it)?.time,
|
||||
),
|
||||
)
|
||||
}
|
||||
): List<RelayBasedFilter> {
|
||||
val relays = channel.relays().toSet()
|
||||
val directory =
|
||||
relays.map {
|
||||
RelayBasedFilter(
|
||||
relay = it,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = RELAY_GROUP_METADATA_KINDS,
|
||||
tags = mapOf("d" to listOf(channel.groupId.id)),
|
||||
since = since?.get(it)?.time,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Back-fill the bodies of pinned messages by id from the host relay. A pin can point at a
|
||||
// message older than the 200-item timeline window, which the `h`-scoped timeline filter would
|
||||
// never return — without this the pin bar shows a blank preview and can't jump to it. No `since`:
|
||||
// pinned events are immutable, so we want them regardless of age.
|
||||
val pinnedIds = channel.pinnedEventIds
|
||||
val pins =
|
||||
if (pinnedIds.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
relays.map { RelayBasedFilter(relay = it, filter = Filter(ids = pinnedIds)) }
|
||||
}
|
||||
|
||||
return directory + pins
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user