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>,
onProgressChange: (Float) -> Unit,
) {
val progressState = remember(progress) { progress.coerceIn(MIN_PROGRESS, MAX_PROGRESS) }
val spikeWidthState =
remember(spikeWidth) { spikeWidth.coerceIn(MinSpikeWidthDp, MaxSpikeWidthDp) }
val spikePaddingState =
remember(spikePadding) { spikePadding.coerceIn(MinSpikePaddingDp, MaxSpikePaddingDp) }
val spikeRadiusState =
remember(spikeRadius) { spikeRadius.coerceIn(MinSpikeRadiusDp, MaxSpikeRadiusDp) }
val spikeTotalWidthState =
remember(spikeWidth, spikePadding) { spikeWidthState + spikePaddingState }
val progressState = progress.coerceIn(MIN_PROGRESS, MAX_PROGRESS)
val spikeWidthState = spikeWidth.coerceIn(MinSpikeWidthDp, MaxSpikeWidthDp)
val spikePaddingState = spikePadding.coerceIn(MinSpikePaddingDp, MaxSpikePaddingDp)
val spikeRadiusState = spikeRadius.coerceIn(MinSpikeRadiusDp, MaxSpikeRadiusDp)
val spikeTotalWidthState = spikeWidthState + spikePaddingState
var canvasSize by remember { mutableStateOf(Size(0f, 0f)) }
var spikes by remember { mutableFloatStateOf(0F) }
val spikesAmplitudes =
@@ -334,8 +334,8 @@ private fun DialogContent(
AnimatedVisibility(
visible = controllerVisible.value,
enter = remember { fadeIn() },
exit = remember { fadeOut() },
enter = fadeIn(),
exit = fadeOut(),
// Also fade with the grow animation so controls appear/disappear alongside it.
modifier = Modifier.graphicsLayer { alpha = progress().coerceIn(0f, 1f) },
) {
@@ -404,8 +404,8 @@ fun LocalImageView(
AnimatedVisibility(
visible = controllerVisible.value,
modifier = Modifier.align(Alignment.TopEnd),
enter = remember { fadeIn() },
exit = remember { fadeOut() },
enter = fadeIn(),
exit = fadeOut(),
) {
Box(Modifier.align(Alignment.TopEnd), contentAlignment = Alignment.TopEnd) {
HashVerificationSymbol(it)
@@ -649,8 +649,8 @@ fun ShowHashAnimated(
AnimatedVisibility(
visible = controllerVisible.value,
modifier = modifier,
enter = remember { fadeIn() },
exit = remember { fadeOut() },
enter = fadeIn(),
exit = fadeOut(),
) {
Box(modifier, contentAlignment = Alignment.TopEnd) {
ShowHash(content)
@@ -332,7 +332,7 @@ fun RenderZapGallery(
modifier = WidthAuthorPictureModifier,
) {
ZappedIcon(
modifier = remember { Modifier.size(Size25dp).align(Alignment.TopEnd) },
modifier = Modifier.size(Size25dp).align(Alignment.TopEnd),
)
}
@@ -353,7 +353,7 @@ fun RenderBoostGallery(
modifier = NotificationIconModifierSmaller,
) {
RepostedIcon(
modifier = remember { Modifier.size(Size20dp).align(Alignment.TopEnd) },
modifier = Modifier.size(Size20dp).align(Alignment.TopEnd),
)
}
@@ -374,7 +374,7 @@ fun RenderBoostGallery(
modifier = NotificationIconModifierSmaller,
) {
RepostedIcon(
modifier = remember { Modifier.size(Size20dp).align(Alignment.TopEnd) },
modifier = Modifier.size(Size20dp).align(Alignment.TopEnd),
)
}
@@ -1513,7 +1513,7 @@ fun SecondUserInfoRow(
verticalAlignment = CenterVertically,
modifier = UserNameMaxRowHeight,
) {
Column(modifier = remember { Modifier.weight(1f) }) {
Column(modifier = Modifier.weight(1f)) {
if (noteEvent is IForkableEvent && noteEvent.isAFork()) {
ShowForkInformation(noteEvent, Modifier, accountViewModel, nav)
} else {
@@ -28,7 +28,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
@@ -136,7 +135,7 @@ fun UserComposeNoAction(
) {
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) {
UsernameDisplay(baseUser, accountViewModel = accountViewModel)
}
@@ -29,7 +29,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
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)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserBoostModel(model)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserReactionModel(model)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
UserZapModel(model)
}
}
@@ -30,7 +30,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -78,7 +77,7 @@ fun ZapUserSetCompose(
modifier = Size55Modifier,
) {
ZappedIcon(
remember { Modifier.size(Size25dp).align(Alignment.TopEnd) },
Modifier.size(Size25dp).align(Alignment.TopEnd),
)
}
}
@@ -103,7 +102,7 @@ fun ZapUserSetCompose(
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) }
AboutDisplay(zapSetCard.user, accountViewModel)
@@ -167,7 +167,7 @@ fun LongPublicChatChannelHeader(
modifier = Modifier.width(75.dp),
)
Spacer(DoubleHorzSpacer)
NormalTimeAgo(note, remember { Modifier.weight(1f) })
NormalTimeAgo(note, Modifier.weight(1f))
MoreOptionsButton(note, null, accountViewModel, nav)
}
}
@@ -116,7 +116,7 @@ fun LongLiveActivityChannelHeader(
modifier = Modifier.width(75.dp),
)
Spacer(DoubleHorzSpacer)
NormalTimeAgo(note, remember { Modifier.weight(1f) })
NormalTimeAgo(note, Modifier.weight(1f))
MoreOptionsButton(note, null, accountViewModel, nav)
}
}
@@ -30,7 +30,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -57,7 +56,7 @@ fun DisplayReplyingToNote(
.animateContentSize(),
) {
if (replyingNote != null) {
Column(remember { Modifier.weight(1f) }) {
Column(Modifier.weight(1f)) {
ChatroomMessageCompose(
baseNote = replyingNote,
null,
@@ -535,7 +535,7 @@ private fun FullBleedNoteCompose(
Row(verticalAlignment = Alignment.CenterVertically) {
Column(
remember { Modifier.weight(1f) },
Modifier.weight(1f),
) {
if (noteEvent is IForkableEvent && noteEvent.isAFork()) {
ShowForkInformation(noteEvent, Modifier, accountViewModel, nav)
@@ -79,7 +79,7 @@ fun BookmarksScreen(
onZapFeedback: (ZapFeedback) -> Unit = {},
) {
val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys }
val connectedRelays = relayStatuses.keys
val scope = rememberCoroutineScope()
// Tab state
@@ -280,7 +280,7 @@ fun FeedScreen(
val followedUsers by localCache.followedUsers.collectAsState()
// 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)
val relayCategories = LocalRelayCategories.current
@@ -115,7 +115,7 @@ fun NotificationsScreen(
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null,
) {
val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys }
val connectedRelays = relayStatuses.keys
val scope = rememberCoroutineScope()
val notificationState =
remember {
@@ -183,7 +183,7 @@ fun ReadsScreen(
onZapFeedback: (ZapFeedback) -> Unit = {},
) {
val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys }
val connectedRelays = relayStatuses.keys
val scope = rememberCoroutineScope()
val eventState =
@@ -130,7 +130,7 @@ fun SearchScreen(
val connectedRelays by relayManager.connectedRelays.collectAsState()
val relayStatuses by relayManager.relayStatuses.collectAsState()
val allRelayUrls = remember(relayStatuses) { relayStatuses.keys }
val allRelayUrls = relayStatuses.keys
val relayCategories = LocalRelayCategories.current
val searchRelays by relayCategories.searchRelays.collectAsState()
val displayText by state.displayText.collectAsState()
@@ -90,7 +90,7 @@ fun ThreadScreen(
onReply: (Event) -> Unit = {},
) {
val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys }
val connectedRelays = relayStatuses.keys
// Lightbox state
var lightboxState by remember { mutableStateOf<LightboxState?>(null) }
@@ -122,7 +122,7 @@ fun UserProfileScreen(
onZapFeedback: (ZapFeedback) -> Unit = {},
) {
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
val cachedUser = remember(pubKeyHex) { localCache.getUserIfExists(pubKeyHex) }
@@ -78,7 +78,7 @@ fun NewDmDialog(
val relaySearchResults by searchState.relaySearchResults.collectAsState()
val isSearchingRelays by searchState.isSearchingRelays.collectAsState()
val relayStatuses by relayManager.relayStatuses.collectAsState()
val connectedRelays = remember(relayStatuses) { relayStatuses.keys }
val connectedRelays = relayStatuses.keys
val focusRequester = remember { FocusRequester() }
// NIP-50 relay search when local cache has few/no results