From ee217a42dc6767a18f01e428905f4aef0a4a3600 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 22:41:43 +0000 Subject: [PATCH] perf(commons): remember derived values in the moved note cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit follow-up. These renderers sit in scrolling feeds; each recomputed event parses / string builds directly in the composable body instead of caching them against the (immutable) event — the CLAUDE.md rule-#4 pattern. The cards are already skippable (immutable event params + strong skipping), so this is work redone per composition (each item scrolling into view), not per frame; still worth removing. These patterns pre-existed in the Android originals and were carried over faithfully — this cleans them up now that the code is shared. - RelayDiscoveryCard: the heaviest — 6 `joinToString` + a `.sorted()` ran in the body. Now each display string (network / relay-type / requirements / supported-NIPs / accepted-kinds / geohashes, the requirements lock flag, and the relay-URL displayUrl()) is `remember`ed off its parsed list; the row visibility checks still key off the original lists so behavior is identical. - CalendarRsvpCard: `status` / `calendarEventAddress` / `freebusy` parses now `remember(event)` instead of re-scanning tags every composition. - CalendarCollectionCard: `title()` and `calendarEventAddresses().size` (which allocated a whole List just to read size) now `remember(event)`. - PodcastValueSplits: the `recipients.filter{}` + `totalSplit()` now `remember(value)`. Behavior is unchanged (same keys, conditions, and outputs). Verified: :commons:compileKotlinJvm + :amethyst:compileFdroidDebugKotlin pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Gmrt3jwYPDJ38MJGJ6GaNr --- .../commons/ui/note/CalendarCollectionCard.kt | 7 ++-- .../commons/ui/note/CalendarRsvpCard.kt | 7 ++-- .../commons/ui/note/PodcastValueSplits.kt | 5 +-- .../commons/ui/note/RelayDiscoveryCard.kt | 33 ++++++++++--------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarCollectionCard.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarCollectionCard.kt index aa47b000ee..ba22fa6639 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarCollectionCard.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarCollectionCard.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -46,6 +47,9 @@ import org.jetbrains.compose.resources.pluralStringResource /** Card for a NIP-52 calendar collection (kind 31924): title, description, and event count. */ @Composable fun CalendarCollectionCard(event: CalendarEvent) { + val title = remember(event) { event.title() } + val eventCount = remember(event) { event.calendarEventAddresses().size } + Column(MaterialTheme.colorScheme.replyModifier) { Row( modifier = Modifier.fillMaxWidth().padding(start = 10.dp, end = 10.dp, top = 10.dp), @@ -59,7 +63,7 @@ fun CalendarCollectionCard(event: CalendarEvent) { ) Spacer(modifier = Modifier.size(8.dp)) Text( - text = event.title() ?: "—", + text = title ?: "—", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, maxLines = 2, @@ -80,7 +84,6 @@ fun CalendarCollectionCard(event: CalendarEvent) { } Spacer(modifier = StdVertSpacer) - val eventCount = event.calendarEventAddresses().size Text( text = pluralStringResource(Res.plurals.calendar_collection_count, eventCount, eventCount), style = MaterialTheme.typography.labelMedium, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarRsvpCard.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarRsvpCard.kt index 0fd2e92e63..53068020ce 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarRsvpCard.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/CalendarRsvpCard.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -49,9 +50,9 @@ import org.jetbrains.compose.resources.stringResource /** Card for a NIP-52 calendar RSVP (kind 31925): the going/maybe/not-going status, note, and target. */ @Composable fun CalendarRsvpCard(event: CalendarRSVPEvent) { - val status = event.status() - val targetAddress = event.calendarEventAddress() - val freebusy = event.freebusy() + val status = remember(event) { event.status() } + val targetAddress = remember(event) { event.calendarEventAddress() } + val freebusy = remember(event) { event.freebusy() } val statusLabel = when (status) { diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PodcastValueSplits.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PodcastValueSplits.kt index da7303af44..2374ad973a 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PodcastValueSplits.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PodcastValueSplits.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -60,10 +61,10 @@ import org.jetbrains.compose.resources.stringResource */ @Composable fun PodcastValueSplits(value: PodcastValue) { - val recipients = value.recipients.filter { it.split > 0 || it.address != null } + val recipients = remember(value) { value.recipients.filter { it.split > 0 || it.address != null } } if (recipients.isEmpty()) return - val total = value.totalSplit().takeIf { it > 0 } ?: recipients.size + val total = remember(value, recipients) { value.totalSplit().takeIf { it > 0 } ?: recipients.size } Column( modifier = diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/RelayDiscoveryCard.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/RelayDiscoveryCard.kt index 2ed8a6817f..084cd9f018 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/RelayDiscoveryCard.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/RelayDiscoveryCard.kt @@ -70,17 +70,24 @@ import org.jetbrains.compose.resources.stringResource @OptIn(ExperimentalLayoutApi::class) @Composable fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { - val relayUrl = remember(noteEvent) { noteEvent.relay() } + val relayUrl = remember(noteEvent) { noteEvent.relay()?.displayUrl() } val rttOpen = remember(noteEvent) { noteEvent.rttOpen() } val rttRead = remember(noteEvent) { noteEvent.rttRead() } val rttWrite = remember(noteEvent) { noteEvent.rttWrite() } val networkTypes = remember(noteEvent) { noteEvent.networkTypes() } + val networkValue = remember(networkTypes) { networkTypes.joinToString { it.code } } val relayTypes = remember(noteEvent) { noteEvent.relayTypes() } - val supportedNips = remember(noteEvent) { noteEvent.supportedNips() } + val relayTypeValue = remember(relayTypes) { relayTypes.joinToString() } val requirements = remember(noteEvent) { noteEvent.requirements() } + val requirementsLocked = remember(requirements) { requirements.any { !it.negated } } + val requirementsValue = remember(requirements) { requirements.joinToString { req -> if (req.negated) "!${req.value}" else req.value } } + val supportedNips = remember(noteEvent) { noteEvent.supportedNips() } + val supportedNipsValue = remember(supportedNips) { supportedNips.sorted().joinToString() } val acceptedKinds = remember(noteEvent) { noteEvent.acceptedKinds() } + val acceptedKindsValue = remember(acceptedKinds) { acceptedKinds.joinToString { kind -> if (kind.negated) "!${kind.kind}" else "${kind.kind}" } } val topics = remember(noteEvent) { noteEvent.topics() } val geohashes = remember(noteEvent) { noteEvent.geohashes() } + val geohashesValue = remember(geohashes) { geohashes.joinToString() } Column( modifier = Modifier.fillMaxWidth(), @@ -89,7 +96,7 @@ fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { // Relay URL header if (relayUrl != null) { Text( - text = relayUrl.displayUrl(), + text = relayUrl, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary, @@ -121,7 +128,7 @@ fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { DiscoveryInfoRow( icon = MaterialSymbols.Language, label = stringResource(Res.string.relay_monitor_network), - value = networkTypes.joinToString { it.code }, + value = networkValue, ) } @@ -130,19 +137,16 @@ fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { DiscoveryInfoRow( icon = MaterialSymbols.Dns, label = stringResource(Res.string.relay_monitor_relay_type), - value = relayTypes.joinToString(), + value = relayTypeValue, ) } // Requirements if (requirements.isNotEmpty()) { DiscoveryInfoRow( - icon = if (requirements.any { !it.negated }) MaterialSymbols.Lock else MaterialSymbols.LockOpen, + icon = if (requirementsLocked) MaterialSymbols.Lock else MaterialSymbols.LockOpen, label = stringResource(Res.string.relay_monitor_requirements), - value = - requirements.joinToString { req -> - if (req.negated) "!${req.value}" else req.value - }, + value = requirementsValue, ) } @@ -151,7 +155,7 @@ fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { DiscoveryInfoRow( icon = MaterialSymbols.Numbers, label = stringResource(Res.string.relay_monitor_supported_nips), - value = supportedNips.sorted().joinToString(), + value = supportedNipsValue, ) } @@ -160,10 +164,7 @@ fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { DiscoveryInfoRow( icon = MaterialSymbols.Dns, label = stringResource(Res.string.relay_discovery_accepted_kinds), - value = - acceptedKinds.joinToString { kind -> - if (kind.negated) "!${kind.kind}" else "${kind.kind}" - }, + value = acceptedKindsValue, ) } @@ -196,7 +197,7 @@ fun RelayDiscoveryCard(noteEvent: RelayDiscoveryEvent) { DiscoveryInfoRow( icon = MaterialSymbols.TravelExplore, label = stringResource(Res.string.relay_discovery_geohash), - value = geohashes.joinToString(), + value = geohashesValue, ) }