From 14f8771eb61a522a3fafe4c504ad341a98dcab00 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 4 Jun 2026 12:14:24 -0400 Subject: [PATCH] fix(compose): add contentType to heterogeneous lazy lists Enables Compose to reuse compositions only between items of the same type, improving scroll performance in mixed-content lists. - UserProfileScreenContent: section_header, profile_value, contact_method, empty_state, social_account - UserFlagsEditor: section_header, read_only_row, editable_row - LabsScreenContent: section_header, feature_flag, empty_state, list_item - TokenList: empty_state, header, token_row, reserves, footer --- .../app/lab/internal/LabsScreenContent.kt | 14 +++++++------- .../internal/UserProfileScreenContent.kt | 18 +++++++++--------- .../app/userflags/internal/UserFlagsEditor.kt | 6 ++++-- .../com/flipcash/app/tokens/ui/TokenList.kt | 11 ++++++----- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenContent.kt b/apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenContent.kt index 8e6955d0b..12bc44eb6 100644 --- a/apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenContent.kt +++ b/apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenContent.kt @@ -55,10 +55,10 @@ internal fun LabsScreenContent(viewModel: LabsScreenViewModel) { ).sheetResignmentBehavior(state), contentPadding = PaddingValues(bottom = CodeTheme.dimens.grid.x3), ) { - item { + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_settingsSectionFeatures)) } - items(betaFlags, key = { it.flag.key }) { feature -> + items(betaFlags, key = { it.flag.key }, contentType = { "feature_flag" }) { feature -> if (feature.flag.isOptionFlag) { SettingsOptionRow( title = feature.flag.title, @@ -87,7 +87,7 @@ internal fun LabsScreenContent(viewModel: LabsScreenViewModel) { } if (betaFlags.isEmpty()) { - item { + item(contentType = "empty_state") { Box { Column( modifier = Modifier.align(Alignment.Center), @@ -113,8 +113,8 @@ internal fun LabsScreenContent(viewModel: LabsScreenViewModel) { } } - item { SectionHeader(stringResource(R.string.title_settingsSectionHomeScreen)) } - item { + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_settingsSectionHomeScreen)) } + item(contentType = "list_item") { ListItem( headline = stringResource(R.string.title_settingsButtonOrder), icon = painterResource(R.drawable.ic_bottom_navigation), @@ -124,8 +124,8 @@ internal fun LabsScreenContent(viewModel: LabsScreenViewModel) { } if (isStaff) { - item { SectionHeader(stringResource(R.string.title_settingsSectionDeveloper)) } - item { + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_settingsSectionDeveloper)) } + item(contentType = "list_item") { ListItem( headline = stringResource(R.string.subtitle_settingsUserFlags), icon = rememberVectorPainter(Icons.Default.Token), diff --git a/apps/flipcash/features/myaccount/src/main/kotlin/com/flipcash/app/myaccount/internal/UserProfileScreenContent.kt b/apps/flipcash/features/myaccount/src/main/kotlin/com/flipcash/app/myaccount/internal/UserProfileScreenContent.kt index 535c0488d..29ae9db35 100644 --- a/apps/flipcash/features/myaccount/src/main/kotlin/com/flipcash/app/myaccount/internal/UserProfileScreenContent.kt +++ b/apps/flipcash/features/myaccount/src/main/kotlin/com/flipcash/app/myaccount/internal/UserProfileScreenContent.kt @@ -32,8 +32,8 @@ internal fun UserProfileScreenContent( ) { LazyColumn(modifier = Modifier.fillMaxSize()) { // Display Name section - item { SectionHeader(stringResource(R.string.title_sectionDisplayName)) } - item { + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_sectionDisplayName)) } + item(contentType = "profile_value") { val name = state.displayName if (!name.isNullOrEmpty()) { ProfileValueRow(value = name) @@ -51,8 +51,8 @@ internal fun UserProfileScreenContent( } // Phone section - item { SectionHeader(stringResource(R.string.title_sectionPhone)) } - item { + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_sectionPhone)) } + item(contentType = "contact_method") { if (state.phoneNumber != null) { ContactMethodRow( value = state.phoneNumber, @@ -71,8 +71,8 @@ internal fun UserProfileScreenContent( } // Email section - item { SectionHeader(stringResource(R.string.title_sectionEmail)) } - item { + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_sectionEmail)) } + item(contentType = "contact_method") { if (state.emailAddress != null) { ContactMethodRow( value = state.emailAddress, @@ -89,9 +89,9 @@ internal fun UserProfileScreenContent( } // Social Accounts section - item { SectionHeader(stringResource(R.string.title_sectionSocialAccounts)) } + item(contentType = "section_header") { SectionHeader(stringResource(R.string.title_sectionSocialAccounts)) } if (state.socialAccounts.isEmpty()) { - item { + item(contentType = "empty_state") { Text( text = stringResource(R.string.subtitle_noSocialAccounts), style = CodeTheme.typography.textMedium, @@ -103,7 +103,7 @@ internal fun UserProfileScreenContent( ) } } else { - items(state.socialAccounts, key = { (it as? SocialAccount.TwitterX)?.id ?: it }) { account -> + items(state.socialAccounts, key = { (it as? SocialAccount.TwitterX)?.id ?: it }, contentType = { "social_account" }) { account -> when (account) { is SocialAccount.TwitterX -> SocialAccountRow( account = account, diff --git a/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsEditor.kt b/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsEditor.kt index ada552e55..64200873b 100644 --- a/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsEditor.kt +++ b/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsEditor.kt @@ -74,19 +74,21 @@ private fun UserFlagsEditor( contentPadding = PaddingValues(bottom = CodeTheme.dimens.grid.x3), ) { // Read-only section - item { SectionHeader("Read-Only") } + item(contentType = "section_header") { SectionHeader("Read-Only") } items( items = state.readOnlyEntries, key = { it.label }, + contentType = { "read_only_row" }, ) { entry -> ReadOnlyRow(entry) } // Editable section - item { SectionHeader("Overridable") } + item(contentType = "section_header") { SectionHeader("Overridable") } items( items = state.editableEntries, key = { it.field.label }, + contentType = { "editable_row" }, ) { entry -> EditableRow( modifier = Modifier.fillMaxWidth(), diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenList.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenList.kt index bc7ac1aba..90fe9ab50 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenList.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenList.kt @@ -82,18 +82,19 @@ fun TokenList( state = listState ) { if (tokens != null && tokens.isEmpty() && emptyState != null) { - item { + item(contentType = "empty_state") { emptyState() } } else { header?.let { content -> - item { + item(contentType = "header") { content() } } items( items = filteredTokens.orEmpty(), - key = { item -> item.token.address.base58() }) { item -> + key = { item -> item.token.address.base58() }, + contentType = { "token_row" }) { item -> TokenBalanceRow( modifier = Modifier .fillParentMaxWidth() @@ -113,7 +114,7 @@ fun TokenList( Fiat(0.0, cashReserves.rate.currency) ) ) { - item { + item(contentType = "reserves") { it(Mint.usdf, cashReserves) HorizontalDivider( modifier = Modifier.padding(bottom = CodeTheme.dimens.inset), @@ -124,7 +125,7 @@ fun TokenList( } footer?.let { - item { + item(contentType = "footer") { Box(modifier = Modifier.alpha(if (footerSettled) 1f else 0f)) { it(false) }