perf(ui): drop remember wrappers where overhead exceeds savings

Cases removed:
- Trivial Modifier allocations (Modifier.weight/padding/size) — slot table
  cost dominates the cost of building a fresh Modifier each recomposition.
- Map.keys views over relayStatuses on desktop screens — .keys is a
  property read on the same map, no need to memoize.
- coerceIn() arithmetic on AudioWaveform Dp/Float params — two compares
  are cheaper than the slot table read+compare.
- fadeIn()/fadeOut() in AnimatedVisibility — small EnterTransition
  allocations that don't justify the slot table overhead.

Audit-only changes; no behavior changes.

https://claude.ai/code/session_011Ea2pVjwvCEx7X4izwryV4
This commit is contained in:
Claude
2026-04-26 17:02:53 +00:00
parent d589fc4614
commit 6f04edd995
20 changed files with 34 additions and 42 deletions
@@ -78,15 +78,11 @@ fun AudioWaveformReadOnly(
amplitudes: List<Float>, amplitudes: List<Float>,
onProgressChange: (Float) -> Unit, onProgressChange: (Float) -> Unit,
) { ) {
val progressState = remember(progress) { progress.coerceIn(MIN_PROGRESS, MAX_PROGRESS) } val progressState = progress.coerceIn(MIN_PROGRESS, MAX_PROGRESS)
val spikeWidthState = val spikeWidthState = spikeWidth.coerceIn(MinSpikeWidthDp, MaxSpikeWidthDp)
remember(spikeWidth) { spikeWidth.coerceIn(MinSpikeWidthDp, MaxSpikeWidthDp) } val spikePaddingState = spikePadding.coerceIn(MinSpikePaddingDp, MaxSpikePaddingDp)
val spikePaddingState = val spikeRadiusState = spikeRadius.coerceIn(MinSpikeRadiusDp, MaxSpikeRadiusDp)
remember(spikePadding) { spikePadding.coerceIn(MinSpikePaddingDp, MaxSpikePaddingDp) } val spikeTotalWidthState = spikeWidthState + spikePaddingState
val spikeRadiusState =
remember(spikeRadius) { spikeRadius.coerceIn(MinSpikeRadiusDp, MaxSpikeRadiusDp) }
val spikeTotalWidthState =
remember(spikeWidth, spikePadding) { spikeWidthState + spikePaddingState }
var canvasSize by remember { mutableStateOf(Size(0f, 0f)) } var canvasSize by remember { mutableStateOf(Size(0f, 0f)) }
var spikes by remember { mutableFloatStateOf(0F) } var spikes by remember { mutableFloatStateOf(0F) }
val spikesAmplitudes = val spikesAmplitudes =
@@ -334,8 +334,8 @@ private fun DialogContent(
AnimatedVisibility( AnimatedVisibility(
visible = controllerVisible.value, visible = controllerVisible.value,
enter = remember { fadeIn() }, enter = fadeIn(),
exit = remember { fadeOut() }, exit = fadeOut(),
// Also fade with the grow animation so controls appear/disappear alongside it. // Also fade with the grow animation so controls appear/disappear alongside it.
modifier = Modifier.graphicsLayer { alpha = progress().coerceIn(0f, 1f) }, modifier = Modifier.graphicsLayer { alpha = progress().coerceIn(0f, 1f) },
) { ) {
@@ -404,8 +404,8 @@ fun LocalImageView(
AnimatedVisibility( AnimatedVisibility(
visible = controllerVisible.value, visible = controllerVisible.value,
modifier = Modifier.align(Alignment.TopEnd), modifier = Modifier.align(Alignment.TopEnd),
enter = remember { fadeIn() }, enter = fadeIn(),
exit = remember { fadeOut() }, exit = fadeOut(),
) { ) {
Box(Modifier.align(Alignment.TopEnd), contentAlignment = Alignment.TopEnd) { Box(Modifier.align(Alignment.TopEnd), contentAlignment = Alignment.TopEnd) {
HashVerificationSymbol(it) HashVerificationSymbol(it)
@@ -649,8 +649,8 @@ fun ShowHashAnimated(
AnimatedVisibility( AnimatedVisibility(
visible = controllerVisible.value, visible = controllerVisible.value,
modifier = modifier, modifier = modifier,
enter = remember { fadeIn() }, enter = fadeIn(),
exit = remember { fadeOut() }, exit = fadeOut(),
) { ) {
Box(modifier, contentAlignment = Alignment.TopEnd) { Box(modifier, contentAlignment = Alignment.TopEnd) {
ShowHash(content) ShowHash(content)
@@ -332,7 +332,7 @@ fun RenderZapGallery(
modifier = WidthAuthorPictureModifier, modifier = WidthAuthorPictureModifier,
) { ) {
ZappedIcon( ZappedIcon(
modifier = remember { Modifier.size(Size25dp).align(Alignment.TopEnd) }, modifier = Modifier.size(Size25dp).align(Alignment.TopEnd),
) )
} }
@@ -353,7 +353,7 @@ fun RenderBoostGallery(
modifier = NotificationIconModifierSmaller, modifier = NotificationIconModifierSmaller,
) { ) {
RepostedIcon( RepostedIcon(
modifier = remember { Modifier.size(Size20dp).align(Alignment.TopEnd) }, modifier = Modifier.size(Size20dp).align(Alignment.TopEnd),
) )
} }
@@ -374,7 +374,7 @@ fun RenderBoostGallery(
modifier = NotificationIconModifierSmaller, modifier = NotificationIconModifierSmaller,
) { ) {
RepostedIcon( RepostedIcon(
modifier = remember { Modifier.size(Size20dp).align(Alignment.TopEnd) }, modifier = Modifier.size(Size20dp).align(Alignment.TopEnd),
) )
} }
@@ -1513,7 +1513,7 @@ fun SecondUserInfoRow(
verticalAlignment = CenterVertically, verticalAlignment = CenterVertically,
modifier = UserNameMaxRowHeight, modifier = UserNameMaxRowHeight,
) { ) {
Column(modifier = remember { Modifier.weight(1f) }) { Column(modifier = Modifier.weight(1f)) {
if (noteEvent is IForkableEvent && noteEvent.isAFork()) { if (noteEvent is IForkableEvent && noteEvent.isAFork()) {
ShowForkInformation(noteEvent, Modifier, accountViewModel, nav) ShowForkInformation(noteEvent, Modifier, accountViewModel, nav)
} else { } else {
@@ -28,7 +28,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -136,7 +135,7 @@ fun UserComposeNoAction(
) { ) {
UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav) UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
UsernameDisplay(baseUser, accountViewModel = accountViewModel) UsernameDisplay(baseUser, accountViewModel = accountViewModel)
} }
@@ -29,7 +29,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -75,19 +74,19 @@ fun UserReactionsRow(
) )
} }
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) { Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserReplyModel(model) UserReplyModel(model)
} }
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) { Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserBoostModel(model) UserBoostModel(model)
} }
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) { Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserReactionModel(model) UserReactionModel(model)
} }
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) { Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserZapModel(model) UserZapModel(model)
} }
} }
@@ -30,7 +30,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -78,7 +77,7 @@ fun ZapUserSetCompose(
modifier = Size55Modifier, modifier = Size55Modifier,
) { ) {
ZappedIcon( ZappedIcon(
remember { Modifier.size(Size25dp).align(Alignment.TopEnd) }, Modifier.size(Size25dp).align(Alignment.TopEnd),
) )
} }
} }
@@ -103,7 +102,7 @@ fun ZapUserSetCompose(
nav = nav, nav = nav,
) )
Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user, accountViewModel = accountViewModel) } Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user, accountViewModel = accountViewModel) }
AboutDisplay(zapSetCard.user, accountViewModel) AboutDisplay(zapSetCard.user, accountViewModel)
@@ -167,7 +167,7 @@ fun LongPublicChatChannelHeader(
modifier = Modifier.width(75.dp), modifier = Modifier.width(75.dp),
) )
Spacer(DoubleHorzSpacer) Spacer(DoubleHorzSpacer)
NormalTimeAgo(note, remember { Modifier.weight(1f) }) NormalTimeAgo(note, Modifier.weight(1f))
MoreOptionsButton(note, null, accountViewModel, nav) MoreOptionsButton(note, null, accountViewModel, nav)
} }
} }
@@ -116,7 +116,7 @@ fun LongLiveActivityChannelHeader(
modifier = Modifier.width(75.dp), modifier = Modifier.width(75.dp),
) )
Spacer(DoubleHorzSpacer) Spacer(DoubleHorzSpacer)
NormalTimeAgo(note, remember { Modifier.weight(1f) }) NormalTimeAgo(note, Modifier.weight(1f))
MoreOptionsButton(note, null, accountViewModel, nav) MoreOptionsButton(note, null, accountViewModel, nav)
} }
} }
@@ -30,7 +30,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -57,7 +56,7 @@ fun DisplayReplyingToNote(
.animateContentSize(), .animateContentSize(),
) { ) {
if (replyingNote != null) { if (replyingNote != null) {
Column(remember { Modifier.weight(1f) }) { Column(Modifier.weight(1f)) {
ChatroomMessageCompose( ChatroomMessageCompose(
baseNote = replyingNote, baseNote = replyingNote,
null, null,
@@ -535,7 +535,7 @@ private fun FullBleedNoteCompose(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Column( Column(
remember { Modifier.weight(1f) }, Modifier.weight(1f),
) { ) {
if (noteEvent is IForkableEvent && noteEvent.isAFork()) { if (noteEvent is IForkableEvent && noteEvent.isAFork()) {
ShowForkInformation(noteEvent, Modifier, accountViewModel, nav) ShowForkInformation(noteEvent, Modifier, accountViewModel, nav)
@@ -79,7 +79,7 @@ fun BookmarksScreen(
onZapFeedback: (ZapFeedback) -> Unit = {}, onZapFeedback: (ZapFeedback) -> Unit = {},
) { ) {
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys } val connectedRelays = relayStatuses.keys
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// Tab state // Tab state
@@ -280,7 +280,7 @@ fun FeedScreen(
val followedUsers by localCache.followedUsers.collectAsState() val followedUsers by localCache.followedUsers.collectAsState()
// Available relay URLs — subscribe triggers connection on-demand // Available relay URLs — subscribe triggers connection on-demand
val allRelayUrls = remember(relayStatuses) { relayStatuses.keys } val allRelayUrls = relayStatuses.keys
// Feed relays from relay categories (NIP-65 outbox, minus blocked, with fallback) // Feed relays from relay categories (NIP-65 outbox, minus blocked, with fallback)
val relayCategories = LocalRelayCategories.current val relayCategories = LocalRelayCategories.current
@@ -115,7 +115,7 @@ fun NotificationsScreen(
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null, subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null,
) { ) {
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys } val connectedRelays = relayStatuses.keys
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val notificationState = val notificationState =
remember { remember {
@@ -183,7 +183,7 @@ fun ReadsScreen(
onZapFeedback: (ZapFeedback) -> Unit = {}, onZapFeedback: (ZapFeedback) -> Unit = {},
) { ) {
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys } val connectedRelays = relayStatuses.keys
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val eventState = val eventState =
@@ -130,7 +130,7 @@ fun SearchScreen(
val connectedRelays by relayManager.connectedRelays.collectAsState() val connectedRelays by relayManager.connectedRelays.collectAsState()
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val allRelayUrls = remember(relayStatuses) { relayStatuses.keys } val allRelayUrls = relayStatuses.keys
val relayCategories = LocalRelayCategories.current val relayCategories = LocalRelayCategories.current
val searchRelays by relayCategories.searchRelays.collectAsState() val searchRelays by relayCategories.searchRelays.collectAsState()
val displayText by state.displayText.collectAsState() val displayText by state.displayText.collectAsState()
@@ -90,7 +90,7 @@ fun ThreadScreen(
onReply: (Event) -> Unit = {}, onReply: (Event) -> Unit = {},
) { ) {
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys } val connectedRelays = relayStatuses.keys
// Lightbox state // Lightbox state
var lightboxState by remember { mutableStateOf<LightboxState?>(null) } var lightboxState by remember { mutableStateOf<LightboxState?>(null) }
@@ -122,7 +122,7 @@ fun UserProfileScreen(
onZapFeedback: (ZapFeedback) -> Unit = {}, onZapFeedback: (ZapFeedback) -> Unit = {},
) { ) {
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys } val connectedRelays = relayStatuses.keys
// User metadata — seed from cache so returning to profile is instant // User metadata — seed from cache so returning to profile is instant
val cachedUser = remember(pubKeyHex) { localCache.getUserIfExists(pubKeyHex) } val cachedUser = remember(pubKeyHex) { localCache.getUserIfExists(pubKeyHex) }
@@ -78,7 +78,7 @@ fun NewDmDialog(
val relaySearchResults by searchState.relaySearchResults.collectAsState() val relaySearchResults by searchState.relaySearchResults.collectAsState()
val isSearchingRelays by searchState.isSearchingRelays.collectAsState() val isSearchingRelays by searchState.isSearchingRelays.collectAsState()
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys } val connectedRelays = relayStatuses.keys
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
// NIP-50 relay search when local cache has few/no results // NIP-50 relay search when local cache has few/no results