perf(commons): remember derived values in the moved note cards

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gmrt3jwYPDJ38MJGJ6GaNr
This commit is contained in:
Claude
2026-07-17 22:50:09 +00:00
parent c4957ee8c0
commit ee217a42dc
4 changed files with 29 additions and 23 deletions
@@ -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,
@@ -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) {
@@ -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 =
@@ -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,
)
}