From 8a58fabfddf7dc8f39db7072bb5271ce9e4d8526 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:50:11 +0530 Subject: [PATCH] fix: patch remaining areas generating duplicate keys --- .../app/core/ui/DuplicateSafeLazyKeys.kt | 23 +++++++++++++++++++ .../nuvio/app/core/ui/NuvioShelfComponents.kt | 8 +++---- .../app/features/catalog/CatalogScreen.kt | 8 ++++--- .../features/collection/FolderDetailScreen.kt | 15 +++++++----- .../components/DetailCommentsSection.kt | 7 +++++- .../details/components/DetailSeriesContent.kt | 6 ++++- .../components/DetailTrailersSection.kt | 8 +++---- .../features/player/PlayerEpisodesPanel.kt | 5 +++- .../nuvio/app/features/search/SearchScreen.kt | 8 ++++--- iosApp/Configuration/Version.xcconfig | 2 +- .../xcshareddata/xcschemes/iosApp.xcscheme | 2 -- 11 files changed, 66 insertions(+), 26 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/DuplicateSafeLazyKeys.kt diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/DuplicateSafeLazyKeys.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/DuplicateSafeLazyKeys.kt new file mode 100644 index 00000000..cc3755eb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/DuplicateSafeLazyKeys.kt @@ -0,0 +1,23 @@ +package com.nuvio.app.core.ui + +internal data class DuplicateSafeLazyEntry( + val value: T, + val lazyKey: Any, +) + +internal fun List.withDuplicateSafeLazyKeys(key: (T) -> Any): List> { + val keyCounts = groupingBy(key).eachCount() + val occurrences = mutableMapOf() + + 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) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt index b1b99312..ace10d77 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt @@ -82,10 +82,10 @@ fun NuvioShelfSection( ) { if (key != null) { items( - items = entries, - key = key, - ) { entry -> - itemContent(entry) + items = entries.withDuplicateSafeLazyKeys(key), + key = { entry -> entry.lazyKey }, + ) { keyedEntry -> + itemContent(keyedEntry.value) } } else { items(entries) { entry -> diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt index 9e53063e..fdff2ecd 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt @@ -50,6 +50,7 @@ import com.nuvio.app.core.ui.NuvioBackButton import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.posterCardClickable 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.PosterShape import com.nuvio.app.features.home.stableKey @@ -175,9 +176,10 @@ fun CatalogScreen( } } else { items( - items = uiState.items, - key = { item -> item.stableKey() }, - ) { item -> + items = uiState.items.withDuplicateSafeLazyKeys { item -> item.stableKey() }, + key = { item -> item.lazyKey }, + ) { keyedItem -> + val item = keyedItem.value CatalogPosterTile( item = item, cornerRadiusDp = posterCardStyle.cornerRadiusDp, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt index 07c3cb73..6101d18a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt @@ -54,6 +54,7 @@ import com.nuvio.app.core.ui.NuvioPosterCard import com.nuvio.app.core.ui.NuvioPosterShape import com.nuvio.app.core.ui.NuvioScreenHeader 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.MetaPreview import com.nuvio.app.features.home.PosterShape @@ -275,9 +276,10 @@ private fun TabbedGridContent( verticalArrangement = Arrangement.spacedBy(14.dp), ) { items( - items = selectedTab.items, - key = { item -> item.stableKey() }, - ) { item -> + items = selectedTab.items.withDuplicateSafeLazyKeys { item -> item.stableKey() }, + key = { item -> item.lazyKey }, + ) { keyedItem -> + val item = keyedItem.value NuvioPosterCard( title = item.name, imageUrl = item.poster, @@ -326,9 +328,10 @@ private fun RowsContent( verticalArrangement = Arrangement.spacedBy(16.dp), ) { items( - items = sections, - key = { it.key }, - ) { section -> + items = sections.withDuplicateSafeLazyKeys { it.key }, + key = { it.lazyKey }, + ) { keyedSection -> + val section = keyedSection.value HomeCatalogRowSection( section = section, entries = section.items.take(18), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCommentsSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCommentsSection.kt index 43d740d1..5bc7112a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCommentsSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCommentsSection.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.nuvio.app.core.ui.withDuplicateSafeLazyKeys import com.nuvio.app.features.trakt.TraktCommentReview import kotlinx.coroutines.flow.distinctUntilChanged import nuvio.composeapp.generated.resources.* @@ -122,7 +123,11 @@ fun DetailCommentsSection( state = listState, 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( review = review, onClick = { onCommentClick(review) }, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt index a463e5a2..485c729a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -582,7 +583,10 @@ private fun EpisodeHorizontalRow( contentPadding = PaddingValues(horizontal = rowMetrics.rowHorizontalPadding, vertical = rowMetrics.rowVerticalPadding), 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( parentMetaId = parentMetaId, seasonNumber = episode.season, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailTrailersSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailTrailersSection.kt index 0edc1a3c..e9ef5fa8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailTrailersSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailTrailersSection.kt @@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width 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.material.icons.Icons import androidx.compose.material.icons.filled.ExpandMore @@ -158,10 +158,10 @@ fun DetailTrailersSection( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(sizing.cardSpacing), ) { - items( + itemsIndexed( items = selectedTrailers, - key = { trailer -> "${trailer.type}-${trailer.id}-${trailer.seasonNumber ?: 0}" }, - ) { trailer -> + key = { index, trailer -> "${trailer.type}-${trailer.id}-${trailer.seasonNumber ?: 0}#$index" }, + ) { _, trailer -> TrailerCard( trailer = trailer, cardWidth = sizing.cardWidth, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt index fc675a39..032fc605 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt @@ -291,7 +291,10 @@ private fun EpisodesListSubView( verticalArrangement = Arrangement.spacedBy(4.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 EpisodeRow( episode = episode, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt index c127cf3c..45e335eb 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt @@ -44,6 +44,7 @@ import com.nuvio.app.core.ui.NuvioInputField import com.nuvio.app.core.ui.NuvioScreen import com.nuvio.app.core.ui.NuvioNetworkOfflineCard 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.home.MetaPreview import com.nuvio.app.features.home.components.HomeCatalogRowSection @@ -303,9 +304,10 @@ fun SearchScreen( else -> { items( - items = uiState.sections, - key = { section -> section.key }, - ) { section -> + items = uiState.sections.withDuplicateSafeLazyKeys { section -> section.key }, + key = { section -> section.lazyKey }, + ) { keyedSection -> + val section = keyedSection.value HomeCatalogRowSection( section = section, modifier = Modifier.padding(bottom = 12.dp), diff --git a/iosApp/Configuration/Version.xcconfig b/iosApp/Configuration/Version.xcconfig index 29d28954..e702a219 100644 --- a/iosApp/Configuration/Version.xcconfig +++ b/iosApp/Configuration/Version.xcconfig @@ -1,3 +1,3 @@ CURRENT_PROJECT_VERSION=48 -MARKETING_VERSION=0.1.12 +MARKETING_VERSION=0.1.0 diff --git a/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme b/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme index e171e6d7..9401d693 100644 --- a/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme +++ b/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme @@ -29,8 +29,6 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> - -