fix: patch remaining areas generating duplicate keys

This commit is contained in:
tapframe 2026-04-30 21:50:11 +05:30
parent 0a663560d8
commit 8a58fabfdd
11 changed files with 66 additions and 26 deletions

View file

@ -0,0 +1,23 @@
package com.nuvio.app.core.ui
internal data class DuplicateSafeLazyEntry<T>(
val value: T,
val lazyKey: Any,
)
internal fun <T> List<T>.withDuplicateSafeLazyKeys(key: (T) -> Any): List<DuplicateSafeLazyEntry<T>> {
val keyCounts = groupingBy(key).eachCount()
val occurrences = mutableMapOf<Any, Int>()
return map { entry ->
val baseKey = key(entry)
val lazyKey = if (keyCounts[baseKey] == 1) {
baseKey
} else {
val occurrence = occurrences.getOrElse(baseKey) { 0 }
occurrences[baseKey] = occurrence + 1
"$baseKey#$occurrence"
}
DuplicateSafeLazyEntry(value = entry, lazyKey = lazyKey)
}
}

View file

@ -82,10 +82,10 @@ fun <T> NuvioShelfSection(
) { ) {
if (key != null) { if (key != null) {
items( items(
items = entries, items = entries.withDuplicateSafeLazyKeys(key),
key = key, key = { entry -> entry.lazyKey },
) { entry -> ) { keyedEntry ->
itemContent(entry) itemContent(keyedEntry.value)
} }
} else { } else {
items(entries) { entry -> items(entries) { entry ->

View file

@ -50,6 +50,7 @@ import com.nuvio.app.core.ui.NuvioBackButton
import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.rememberPosterCardStyleUiState
import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.core.ui.posterCardClickable
import com.nuvio.app.core.ui.nuvioSafeBottomPadding import com.nuvio.app.core.ui.nuvioSafeBottomPadding
import com.nuvio.app.core.ui.withDuplicateSafeLazyKeys
import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.PosterShape import com.nuvio.app.features.home.PosterShape
import com.nuvio.app.features.home.stableKey import com.nuvio.app.features.home.stableKey
@ -175,9 +176,10 @@ fun CatalogScreen(
} }
} else { } else {
items( items(
items = uiState.items, items = uiState.items.withDuplicateSafeLazyKeys { item -> item.stableKey() },
key = { item -> item.stableKey() }, key = { item -> item.lazyKey },
) { item -> ) { keyedItem ->
val item = keyedItem.value
CatalogPosterTile( CatalogPosterTile(
item = item, item = item,
cornerRadiusDp = posterCardStyle.cornerRadiusDp, cornerRadiusDp = posterCardStyle.cornerRadiusDp,

View file

@ -54,6 +54,7 @@ import com.nuvio.app.core.ui.NuvioPosterCard
import com.nuvio.app.core.ui.NuvioPosterShape import com.nuvio.app.core.ui.NuvioPosterShape
import com.nuvio.app.core.ui.NuvioScreenHeader import com.nuvio.app.core.ui.NuvioScreenHeader
import com.nuvio.app.core.ui.nuvioSafeBottomPadding import com.nuvio.app.core.ui.nuvioSafeBottomPadding
import com.nuvio.app.core.ui.withDuplicateSafeLazyKeys
import com.nuvio.app.features.home.HomeCatalogSection import com.nuvio.app.features.home.HomeCatalogSection
import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.PosterShape import com.nuvio.app.features.home.PosterShape
@ -275,9 +276,10 @@ private fun TabbedGridContent(
verticalArrangement = Arrangement.spacedBy(14.dp), verticalArrangement = Arrangement.spacedBy(14.dp),
) { ) {
items( items(
items = selectedTab.items, items = selectedTab.items.withDuplicateSafeLazyKeys { item -> item.stableKey() },
key = { item -> item.stableKey() }, key = { item -> item.lazyKey },
) { item -> ) { keyedItem ->
val item = keyedItem.value
NuvioPosterCard( NuvioPosterCard(
title = item.name, title = item.name,
imageUrl = item.poster, imageUrl = item.poster,
@ -326,9 +328,10 @@ private fun RowsContent(
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
) { ) {
items( items(
items = sections, items = sections.withDuplicateSafeLazyKeys { it.key },
key = { it.key }, key = { it.lazyKey },
) { section -> ) { keyedSection ->
val section = keyedSection.value
HomeCatalogRowSection( HomeCatalogRowSection(
section = section, section = section,
entries = section.items.take(18), entries = section.items.take(18),

View file

@ -38,6 +38,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.nuvio.app.core.ui.withDuplicateSafeLazyKeys
import com.nuvio.app.features.trakt.TraktCommentReview import com.nuvio.app.features.trakt.TraktCommentReview
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import nuvio.composeapp.generated.resources.* import nuvio.composeapp.generated.resources.*
@ -122,7 +123,11 @@ fun DetailCommentsSection(
state = listState, state = listState,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
items(comments, key = { it.id }) { review -> items(
items = comments.withDuplicateSafeLazyKeys { it.id },
key = { it.lazyKey },
) { keyedReview ->
val review = keyedReview.value
CommentCard( CommentCard(
review = review, review = review,
onClick = { onCommentClick(review) }, onClick = { onCommentClick(review) },

View file

@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@ -582,7 +583,10 @@ private fun EpisodeHorizontalRow(
contentPadding = PaddingValues(horizontal = rowMetrics.rowHorizontalPadding, vertical = rowMetrics.rowVerticalPadding), contentPadding = PaddingValues(horizontal = rowMetrics.rowHorizontalPadding, vertical = rowMetrics.rowVerticalPadding),
horizontalArrangement = Arrangement.spacedBy(rowMetrics.itemSpacing), horizontalArrangement = Arrangement.spacedBy(rowMetrics.itemSpacing),
) { ) {
items(episodes, key = { it.id }) { episode -> itemsIndexed(
items = episodes,
key = { index, episode -> "${episode.season}:${episode.episode}:${episode.id}#$index" },
) { _, episode ->
val episodeVideoId = buildPlaybackVideoId( val episodeVideoId = buildPlaybackVideoId(
parentMetaId = parentMetaId, parentMetaId = parentMetaId,
seasonNumber = episode.season, seasonNumber = episode.season,

View file

@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
@ -158,10 +158,10 @@ fun DetailTrailersSection(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(sizing.cardSpacing), horizontalArrangement = Arrangement.spacedBy(sizing.cardSpacing),
) { ) {
items( itemsIndexed(
items = selectedTrailers, items = selectedTrailers,
key = { trailer -> "${trailer.type}-${trailer.id}-${trailer.seasonNumber ?: 0}" }, key = { index, trailer -> "${trailer.type}-${trailer.id}-${trailer.seasonNumber ?: 0}#$index" },
) { trailer -> ) { _, trailer ->
TrailerCard( TrailerCard(
trailer = trailer, trailer = trailer,
cardWidth = sizing.cardWidth, cardWidth = sizing.cardWidth,

View file

@ -291,7 +291,10 @@ private fun EpisodesListSubView(
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp),
) { ) {
items(seasonEpisodes, key = { "${it.season}:${it.episode}:${it.id}" }) { episode -> itemsIndexed(
items = seasonEpisodes,
key = { index, episode -> "${episode.season}:${episode.episode}:${episode.id}#$index" },
) { _, episode ->
val isCurrent = episode.season == currentSeason && episode.episode == currentEpisode val isCurrent = episode.season == currentSeason && episode.episode == currentEpisode
EpisodeRow( EpisodeRow(
episode = episode, episode = episode,

View file

@ -44,6 +44,7 @@ import com.nuvio.app.core.ui.NuvioInputField
import com.nuvio.app.core.ui.NuvioScreen import com.nuvio.app.core.ui.NuvioScreen
import com.nuvio.app.core.ui.NuvioNetworkOfflineCard import com.nuvio.app.core.ui.NuvioNetworkOfflineCard
import com.nuvio.app.core.ui.NuvioScreenHeader import com.nuvio.app.core.ui.NuvioScreenHeader
import com.nuvio.app.core.ui.withDuplicateSafeLazyKeys
import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.components.HomeCatalogRowSection import com.nuvio.app.features.home.components.HomeCatalogRowSection
@ -303,9 +304,10 @@ fun SearchScreen(
else -> { else -> {
items( items(
items = uiState.sections, items = uiState.sections.withDuplicateSafeLazyKeys { section -> section.key },
key = { section -> section.key }, key = { section -> section.lazyKey },
) { section -> ) { keyedSection ->
val section = keyedSection.value
HomeCatalogRowSection( HomeCatalogRowSection(
section = section, section = section,
modifier = Modifier.padding(bottom = 12.dp), modifier = Modifier.padding(bottom = 12.dp),

View file

@ -1,3 +1,3 @@
CURRENT_PROJECT_VERSION=48 CURRENT_PROJECT_VERSION=48
MARKETING_VERSION=0.1.12 MARKETING_VERSION=0.1.0

View file

@ -29,8 +29,6 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES"> shouldAutocreateTestPlan = "YES">
<Testables>
</Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"