From 1a59fc0a205206f3c2146174c9c90d5c3137d48e Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Mon, 13 Apr 2026 18:22:55 +0530 Subject: [PATCH] feat: poster customization --- .../kotlin/com/nuvio/app/MainActivity.kt | 2 + ...PlatformLocalAccountDataCleaner.android.kt | 1 + .../core/ui/PosterCardStyleStorage.android.kt | 26 ++ .../core/storage/LocalAccountDataCleaner.kt | 2 + .../app/core/sync/ProfileSettingsSync.kt | 10 + .../ui/NuvioContinueWatchingActionSheet.kt | 4 +- .../app/core/ui/NuvioPosterActionSheet.kt | 4 +- .../nuvio/app/core/ui/NuvioShelfComponents.kt | 20 +- .../app/core/ui/PosterCardStyleCompose.kt | 12 + .../app/core/ui/PosterCardStyleRepository.kt | 117 ++++++++ .../app/core/ui/PosterCardStyleStorage.kt | 6 + .../app/features/catalog/CatalogScreen.kt | 14 +- .../features/details/PersonDetailScreen.kt | 5 +- .../details/TmdbEntityBrowseScreen.kt | 5 +- .../components/HomeCollectionRowSection.kt | 4 +- .../home/components/HomeSkeletonLoading.kt | 8 +- .../features/profiles/ProfileRepository.kt | 2 + .../features/search/SearchDiscoverContent.kt | 11 +- .../settings/AppearanceSettingsPage.kt | 10 + .../PosterCustomizationSettingsPage.kt | 261 ++++++++++++++++++ .../app/features/settings/SettingsModels.kt | 5 + .../app/features/settings/SettingsScreen.kt | 20 ++ .../PlatformLocalAccountDataCleaner.ios.kt | 1 + .../app/core/ui/PosterCardStyleStorage.ios.kt | 15 + 24 files changed, 544 insertions(+), 21 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.android.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleCompose.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PosterCustomizationSettingsPage.kt create mode 100644 composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.ios.kt diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt index 822df483..4a618795 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt @@ -32,6 +32,7 @@ import com.nuvio.app.features.settings.ThemeSettingsStorage import com.nuvio.app.features.trakt.TraktAuthStorage import com.nuvio.app.features.trakt.TraktCommentsStorage import com.nuvio.app.features.tmdb.TmdbSettingsStorage +import com.nuvio.app.core.ui.PosterCardStyleStorage import com.nuvio.app.features.watched.WatchedStorage import com.nuvio.app.features.streams.StreamLinkCacheStorage import com.nuvio.app.features.watchprogress.ContinueWatchingEnrichmentStorage @@ -60,6 +61,7 @@ class MainActivity : ComponentActivity() { SearchHistoryStorage.initialize(applicationContext) SeasonViewModeStorage.initialize(applicationContext) ThemeSettingsStorage.initialize(applicationContext) + PosterCardStyleStorage.initialize(applicationContext) TmdbSettingsStorage.initialize(applicationContext) MdbListSettingsStorage.initialize(applicationContext) TraktAuthStorage.initialize(applicationContext) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt index 8f9d2dd4..410029d7 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt @@ -10,6 +10,7 @@ internal actual object PlatformLocalAccountDataCleaner { "nuvio_player_settings", "nuvio_profile_cache", "nuvio_theme_settings", + "nuvio_poster_card_style", "nuvio_mdblist_settings", "nuvio_trakt_auth", "nuvio_watched", diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.android.kt new file mode 100644 index 00000000..eff4dd80 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.android.kt @@ -0,0 +1,26 @@ +package com.nuvio.app.core.ui + +import android.content.Context +import android.content.SharedPreferences +import com.nuvio.app.core.storage.ProfileScopedKey + +actual object PosterCardStyleStorage { + private const val preferencesName = "nuvio_poster_card_style" + private const val payloadKey = "poster_card_style_payload" + + private var preferences: SharedPreferences? = null + + fun initialize(context: Context) { + preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE) + } + + actual fun loadPayload(): String? = + preferences?.getString(ProfileScopedKey.of(payloadKey), null) + + actual fun savePayload(payload: String) { + preferences + ?.edit() + ?.putString(ProfileScopedKey.of(payloadKey), payload) + ?.apply() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt index 13034a2a..ca9f2609 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt @@ -20,6 +20,7 @@ import com.nuvio.app.features.settings.ThemeSettingsRepository import com.nuvio.app.features.streams.StreamContextStore import com.nuvio.app.features.streams.StreamsRepository import com.nuvio.app.features.trakt.TraktAuthRepository +import com.nuvio.app.core.ui.PosterCardStyleRepository import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository import com.nuvio.app.features.watchprogress.WatchProgressRepository import com.nuvio.app.features.watched.WatchedRepository @@ -43,6 +44,7 @@ internal object LocalAccountDataCleaner { EpisodeReleaseNotificationsRepository.clearLocalState() CollectionRepository.clearLocalState() ThemeSettingsRepository.clearLocalState() + PosterCardStyleRepository.clearLocalState() TraktAuthRepository.clearLocalState() PlayerSettingsRepository.clearLocalState() CatalogRepository.clear() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt index aaf9fca0..a56aefb8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt @@ -13,6 +13,8 @@ import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepositor import com.nuvio.app.features.player.PlayerSettingsStorage import com.nuvio.app.features.player.PlayerSettingsRepository import com.nuvio.app.features.profiles.ProfileRepository +import com.nuvio.app.core.ui.PosterCardStyleRepository +import com.nuvio.app.core.ui.PosterCardStyleStorage import com.nuvio.app.features.settings.ThemeSettingsStorage import com.nuvio.app.features.settings.ThemeSettingsRepository import com.nuvio.app.features.tmdb.TmdbSettingsStorage @@ -148,6 +150,7 @@ object ProfileSettingsSync { val signatureFlows = listOf( ThemeSettingsRepository.selectedTheme.map { "theme" }, ThemeSettingsRepository.amoledEnabled.map { "amoled" }, + PosterCardStyleRepository.uiState.map { "poster_card_style" }, PlayerSettingsRepository.uiState.map { "player" }, TmdbSettingsRepository.uiState.map { "tmdb" }, MdbListSettingsRepository.uiState.map { "mdblist" }, @@ -190,6 +193,7 @@ object ProfileSettingsSync { return MobileProfileSettingsBlob( features = MobileProfileSettingsFeatures( themeSettings = ThemeSettingsStorage.exportToSyncPayload(), + posterCardStyleSettingsPayload = PosterCardStyleStorage.loadPayload().orEmpty().trim(), playerSettings = PlayerSettingsStorage.exportToSyncPayload(), tmdbSettings = TmdbSettingsStorage.exportToSyncPayload(), mdbListSettings = MdbListSettingsStorage.exportToSyncPayload(), @@ -207,6 +211,9 @@ object ProfileSettingsSync { ThemeSettingsStorage.replaceFromSyncPayload(blob.features.themeSettings) ThemeSettingsRepository.onProfileChanged() + PosterCardStyleStorage.savePayload(blob.features.posterCardStyleSettingsPayload) + PosterCardStyleRepository.onProfileChanged() + PlayerSettingsStorage.replaceFromSyncPayload(blob.features.playerSettings) PlayerSettingsRepository.onProfileChanged() @@ -231,6 +238,7 @@ object ProfileSettingsSync { private fun ensureRepositoriesLoaded() { ThemeSettingsRepository.ensureLoaded() + PosterCardStyleRepository.ensureLoaded() PlayerSettingsRepository.ensureLoaded() TmdbSettingsRepository.ensureLoaded() MdbListSettingsRepository.ensureLoaded() @@ -249,6 +257,7 @@ object ProfileSettingsSync { private fun currentObservedStateSignature(): String = listOf( "theme=${ThemeSettingsRepository.selectedTheme.value.name}", "amoled=${ThemeSettingsRepository.amoledEnabled.value}", + "poster_card_style=${PosterCardStyleRepository.uiState.value}", "player=${PlayerSettingsRepository.uiState.value}", "tmdb=${TmdbSettingsRepository.uiState.value}", "mdblist=${MdbListSettingsRepository.uiState.value}", @@ -268,6 +277,7 @@ private data class MobileProfileSettingsBlob( @Serializable private data class MobileProfileSettingsFeatures( @SerialName("theme_settings") val themeSettings: JsonObject = JsonObject(emptyMap()), + @SerialName("poster_card_style_settings_payload") val posterCardStyleSettingsPayload: String = "", @SerialName("player_settings") val playerSettings: JsonObject = JsonObject(emptyMap()), @SerialName("tmdb_settings") val tmdbSettings: JsonObject = JsonObject(emptyMap()), @SerialName("mdblist_settings") val mdbListSettings: JsonObject = JsonObject(emptyMap()), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt index 5c2d178d..1cf4db94 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt @@ -103,6 +103,8 @@ fun NuvioContinueWatchingActionSheet( private fun ContinueWatchingSheetHeader( item: ContinueWatchingItem, ) { + val posterCardStyle = rememberPosterCardStyleUiState() + Row( modifier = Modifier .fillMaxWidth() @@ -113,7 +115,7 @@ private fun ContinueWatchingSheetHeader( Box( modifier = Modifier .size(width = 64.dp, height = 92.dp) - .clip(RoundedCornerShape(16.dp)) + .clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center, ) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt index a3fc1897..d3ee69d3 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt @@ -140,6 +140,8 @@ fun NuvioAnimatedWatchedBadge( private fun PosterSheetHeader( item: MetaPreview, ) { + val posterCardStyle = rememberPosterCardStyleUiState() + Row( modifier = Modifier .fillMaxWidth() @@ -150,7 +152,7 @@ private fun PosterSheetHeader( Box( modifier = Modifier .size(width = 64.dp, height = 92.dp) - .clip(RoundedCornerShape(16.dp)) + .clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center, ) { 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 93c36030..4723f1e2 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 @@ -102,15 +102,19 @@ fun NuvioPosterCard( onClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, ) { + val posterCardStyle = rememberPosterCardStyleUiState() + val cardWidth = shape.cardWidth(basePosterWidthDp = posterCardStyle.widthDp) + val cardShape = RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp) + Column( - modifier = modifier.width(shape.cardWidth), + modifier = modifier.width(cardWidth), verticalArrangement = Arrangement.spacedBy(6.dp), ) { Box( modifier = Modifier .fillMaxWidth() .aspectRatio(shape.aspectRatio) - .clip(RoundedCornerShape(16.dp)) + .clip(cardShape) .background(MaterialTheme.colorScheme.surface) .posterCardClickable(onClick = onClick, onLongClick = onLongClick), contentAlignment = Alignment.Center, @@ -254,11 +258,13 @@ private val NuvioPosterShape.aspectRatio: Float NuvioPosterShape.Landscape -> 1.77f } -private val NuvioPosterShape.cardWidth: Dp - get() = when (this) { - NuvioPosterShape.Poster -> 110.dp - NuvioPosterShape.Square -> 110.dp - NuvioPosterShape.Landscape -> 180.dp +private const val LandscapeWidthScale = 180f / 110f + +private fun NuvioPosterShape.cardWidth(basePosterWidthDp: Int): Dp = + when (this) { + NuvioPosterShape.Poster -> basePosterWidthDp.dp + NuvioPosterShape.Square -> basePosterWidthDp.dp + NuvioPosterShape.Landscape -> (basePosterWidthDp * LandscapeWidthScale).dp } @OptIn(ExperimentalFoundationApi::class) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleCompose.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleCompose.kt new file mode 100644 index 00000000..4d69e732 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleCompose.kt @@ -0,0 +1,12 @@ +package com.nuvio.app.core.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.collectAsState + +@Composable +internal fun rememberPosterCardStyleUiState(): PosterCardStyleUiState { + PosterCardStyleRepository.ensureLoaded() + val uiState by PosterCardStyleRepository.uiState.collectAsState() + return uiState +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleRepository.kt new file mode 100644 index 00000000..df7c9b79 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleRepository.kt @@ -0,0 +1,117 @@ +package com.nuvio.app.core.ui + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +internal const val DefaultPosterCardWidthDp = 126 +internal const val DefaultPosterCardHeightDp = 189 +internal const val DefaultPosterCardCornerRadiusDp = 12 + +@Serializable +private data class StoredPosterCardStylePreferences( + val widthDp: Int = DefaultPosterCardWidthDp, + val heightDp: Int = DefaultPosterCardHeightDp, + val cornerRadiusDp: Int = DefaultPosterCardCornerRadiusDp, +) + +data class PosterCardStyleUiState( + val widthDp: Int = DefaultPosterCardWidthDp, + val heightDp: Int = DefaultPosterCardHeightDp, + val cornerRadiusDp: Int = DefaultPosterCardCornerRadiusDp, +) + +object PosterCardStyleRepository { + private val json = Json { + ignoreUnknownKeys = true + encodeDefaults = true + } + + private val _uiState = MutableStateFlow(PosterCardStyleUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private var hasLoaded = false + + fun ensureLoaded() { + if (hasLoaded) return + loadFromDisk() + } + + fun onProfileChanged() { + loadFromDisk() + } + + fun clearLocalState() { + hasLoaded = false + _uiState.value = PosterCardStyleUiState() + } + + fun setWidthDp(widthDp: Int) { + ensureLoaded() + val nextWidth = widthDp + val nextHeight = (nextWidth * 3) / 2 + if (_uiState.value.widthDp == nextWidth && _uiState.value.heightDp == nextHeight) return + _uiState.value = _uiState.value.copy( + widthDp = nextWidth, + heightDp = nextHeight, + ) + persist() + } + + fun setCornerRadiusDp(cornerRadiusDp: Int) { + ensureLoaded() + if (_uiState.value.cornerRadiusDp == cornerRadiusDp) return + _uiState.value = _uiState.value.copy(cornerRadiusDp = cornerRadiusDp) + persist() + } + + fun resetToDefaults() { + ensureLoaded() + if (_uiState.value == PosterCardStyleUiState()) return + _uiState.value = PosterCardStyleUiState() + persist() + } + + private fun loadFromDisk() { + hasLoaded = true + + val payload = PosterCardStyleStorage.loadPayload().orEmpty().trim() + if (payload.isEmpty()) { + _uiState.value = PosterCardStyleUiState() + return + } + + val stored = runCatching { + json.decodeFromString(payload) + }.getOrNull() + + _uiState.value = if (stored != null) { + val widthDp = stored.widthDp.takeIf { it > 0 } ?: DefaultPosterCardWidthDp + val heightDp = stored.heightDp.takeIf { it > 0 } ?: ((widthDp * 3) / 2) + val cornerRadiusDp = stored.cornerRadiusDp.coerceAtLeast(0) + PosterCardStyleUiState( + widthDp = widthDp, + heightDp = heightDp, + cornerRadiusDp = cornerRadiusDp, + ) + } else { + PosterCardStyleUiState() + } + } + + private fun persist() { + PosterCardStyleStorage.savePayload( + json.encodeToString( + StoredPosterCardStylePreferences( + widthDp = _uiState.value.widthDp, + heightDp = _uiState.value.heightDp, + cornerRadiusDp = _uiState.value.cornerRadiusDp, + ), + ), + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.kt new file mode 100644 index 00000000..207a4902 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.kt @@ -0,0 +1,6 @@ +package com.nuvio.app.core.ui + +internal expect object PosterCardStyleStorage { + fun loadPayload(): String? + fun savePayload(payload: String) +} \ No newline at end of file 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 b2d4b790..f2ddd363 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 @@ -47,6 +47,7 @@ import com.nuvio.app.core.ui.NuvioNetworkOfflineCard import coil3.compose.AsyncImage import com.nuvio.app.core.format.formatReleaseDateForDisplay 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.nuvioPlatformExtraBottomPadding import com.nuvio.app.features.home.MetaPreview @@ -70,6 +71,7 @@ fun CatalogScreen( modifier: Modifier = Modifier, ) { val uiState by CatalogRepository.uiState.collectAsStateWithLifecycle() + val posterCardStyle = rememberPosterCardStyleUiState() val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle() val gridState = rememberLazyGridState() var headerHeightPx by remember { mutableIntStateOf(0) } @@ -148,7 +150,9 @@ fun CatalogScreen( verticalArrangement = Arrangement.spacedBy(18.dp), ) { if (uiState.items.isEmpty() && uiState.isLoading) { - items(columns * 3) { CatalogSkeletonTile() } + items(columns * 3) { + CatalogSkeletonTile(cornerRadiusDp = posterCardStyle.cornerRadiusDp) + } } else if (uiState.items.isEmpty()) { item(span = { GridItemSpan(maxLineSpan) }) { CatalogEmptyState( @@ -174,6 +178,7 @@ fun CatalogScreen( ) { item -> CatalogPosterTile( item = item, + cornerRadiusDp = posterCardStyle.cornerRadiusDp, onClick = onPosterClick?.let { { it(item) } }, ) } @@ -242,6 +247,7 @@ private fun CatalogHeader( @Composable private fun CatalogPosterTile( item: MetaPreview, + cornerRadiusDp: Int, onClick: (() -> Unit)? = null, ) { Column( @@ -251,7 +257,7 @@ private fun CatalogPosterTile( modifier = Modifier .fillMaxWidth() .aspectRatio(item.posterShape.catalogAspectRatio()) - .clip(RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surface) .posterCardClickable(onClick = onClick, onLongClick = null), ) { @@ -287,12 +293,12 @@ private fun CatalogPosterTile( } @Composable -private fun CatalogSkeletonTile() { +private fun CatalogSkeletonTile(cornerRadiusDp: Int) { Box( modifier = Modifier .fillMaxWidth() .aspectRatio(0.68f) - .clip(RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surface), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt index 627bc666..40dbdfb3 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import coil3.compose.LocalPlatformContext import coil3.request.ImageRequest +import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.features.details.components.DetailPosterRailSection import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.tmdb.TmdbMetadataService @@ -155,6 +156,7 @@ private fun PersonDetailContent( sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { + val posterCardStyle = rememberPosterCardStyleUiState() val accentColor = MaterialTheme.colorScheme.primary val allCredits = remember(person.movieCredits, person.tvCredits) { @@ -445,6 +447,7 @@ private fun PersonDetailSkeleton( sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { + val posterCardStyle = rememberPosterCardStyleUiState() val accentColor = MaterialTheme.colorScheme.primary val avatarCacheKey = avatarTransitionKey val platformContext = LocalPlatformContext.current @@ -604,7 +607,7 @@ private fun PersonDetailSkeleton( modifier = Modifier .width(110.dp) .height(163.dp) - .clip(RoundedCornerShape(16.dp)) + .clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surfaceVariant), ) Spacer(modifier = Modifier.height(6.dp)) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt index a6a4e072..f1bf3672 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage +import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.features.details.components.DetailPosterRailSection import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.tmdb.TmdbEntityBrowseData @@ -297,6 +298,8 @@ private fun EntityHeroSection( @Composable private fun EntityBrowseSkeleton() { + val posterCardStyle = rememberPosterCardStyleUiState() + Column( modifier = Modifier .fillMaxSize() @@ -354,7 +357,7 @@ private fun EntityBrowseSkeleton() { modifier = Modifier .width(110.dp) .height(163.dp) - .clip(RoundedCornerShape(16.dp)) + .clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt index 4300d3bc..253dc274 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.nuvio.app.core.ui.NuvioShelfSection import com.nuvio.app.core.ui.posterCardClickable +import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.features.collection.Collection import com.nuvio.app.features.collection.CollectionFolder import com.nuvio.app.features.home.PosterShape @@ -86,6 +87,7 @@ private fun CollectionFolderCard( modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, ) { + val posterCardStyle = rememberPosterCardStyleUiState() val shape = folder.posterShape val cardWidth: Dp val aspectRatio: Float @@ -109,7 +111,7 @@ private fun CollectionFolderCard( modifier = modifier.width(cardWidth), verticalArrangement = Arrangement.spacedBy(6.dp), ) { - val shapeCorner = RoundedCornerShape(16.dp) + val shapeCorner = RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp) val imageUrl = collectionFolderCardImageUrl(folder) Card( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt index f034f7f3..ac7902a7 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.nuvio.app.core.ui.rememberPosterCardStyleUiState @Composable private fun rememberHomeSkeletonBrush(): Brush { @@ -180,6 +181,7 @@ fun HomeSkeletonHero( @Composable fun HomeSkeletonRow(modifier: Modifier = Modifier) { val brush = rememberHomeSkeletonBrush() + val posterCardStyle = rememberPosterCardStyleUiState() Column( modifier = modifier.fillMaxWidth(), @@ -209,9 +211,9 @@ fun HomeSkeletonRow(modifier: Modifier = Modifier) { repeat(4) { Box( modifier = Modifier - .width(110.dp) - .height(163.dp) - .clip(RoundedCornerShape(16.dp)) + .width(posterCardStyle.widthDp.dp) + .height(posterCardStyle.heightDp.dp) + .clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)) .background(brush), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt index 522a0c40..b9871947 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt @@ -10,6 +10,7 @@ import com.nuvio.app.features.collection.CollectionRepository import com.nuvio.app.features.downloads.DownloadsRepository import com.nuvio.app.features.details.MetaScreenSettingsRepository import com.nuvio.app.features.home.HomeCatalogSettingsRepository +import com.nuvio.app.core.ui.PosterCardStyleRepository import com.nuvio.app.features.library.LibraryRepository import com.nuvio.app.features.mdblist.MdbListSettingsRepository import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepository @@ -138,6 +139,7 @@ object ProfileRepository { PluginRepository.onProfileChanged(profileIndex) } ThemeSettingsRepository.onProfileChanged() + PosterCardStyleRepository.onProfileChanged() PlayerSettingsRepository.onProfileChanged() HomeCatalogSettingsRepository.onProfileChanged() MetaScreenSettingsRepository.onProfileChanged() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt index e52090cb..f4337812 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt @@ -56,6 +56,7 @@ import com.nuvio.app.core.ui.NuvioBottomSheetDivider import com.nuvio.app.core.ui.NuvioModalBottomSheet import com.nuvio.app.core.ui.dismissNuvioBottomSheet import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding +import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.PosterShape @@ -338,6 +339,8 @@ private fun DiscoverGridRow( onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterLongClick: ((MetaPreview) -> Unit)? = null, ) { + val posterCardStyle = rememberPosterCardStyleUiState() + Row( modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -346,6 +349,7 @@ private fun DiscoverGridRow( items.forEach { item -> DiscoverPosterTile( item = item, + cornerRadiusDp = posterCardStyle.cornerRadiusDp, modifier = Modifier.weight(1f), isWatched = WatchingState.isPosterWatched( watchedKeys = watchedKeys, @@ -365,6 +369,7 @@ private fun DiscoverGridRow( @Composable private fun DiscoverPosterTile( item: MetaPreview, + cornerRadiusDp: Int, modifier: Modifier = Modifier, isWatched: Boolean = false, onClick: (() -> Unit)? = null, @@ -378,7 +383,7 @@ private fun DiscoverPosterTile( modifier = Modifier .fillMaxWidth() .aspectRatio(item.posterShape.discoverAspectRatio()) - .clip(RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surface) .posterCardClickable(onClick = onClick, onLongClick = onLongClick), ) { @@ -424,6 +429,8 @@ private fun DiscoverSkeletonRow( columns: Int, modifier: Modifier = Modifier, ) { + val posterCardStyle = rememberPosterCardStyleUiState() + Row( modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -433,7 +440,7 @@ private fun DiscoverSkeletonRow( modifier = Modifier .weight(1f) .aspectRatio(0.68f) - .clip(RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)) .background(MaterialTheme.colorScheme.surface), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppearanceSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppearanceSettingsPage.kt index bf452f93..96239860 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppearanceSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppearanceSettingsPage.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.rounded.Style +import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -41,6 +42,7 @@ internal fun LazyListScope.appearanceSettingsContent( amoledEnabled: Boolean, onAmoledToggle: (Boolean) -> Unit, onContinueWatchingClick: () -> Unit, + onPosterCustomizationClick: () -> Unit, ) { item { SettingsSection( @@ -101,6 +103,14 @@ internal fun LazyListScope.appearanceSettingsContent( isTablet = isTablet, onClick = onContinueWatchingClick, ) + SettingsGroupDivider(isTablet = isTablet) + SettingsNavigationRow( + title = "Poster Customization", + description = "Adjust shared poster card width and corner radius presets.", + icon = Icons.Rounded.Tune, + isTablet = isTablet, + onClick = onPosterCustomizationClick, + ) } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PosterCustomizationSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PosterCustomizationSettingsPage.kt new file mode 100644 index 00000000..d1750795 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PosterCustomizationSettingsPage.kt @@ -0,0 +1,261 @@ +package com.nuvio.app.features.settings + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.nuvio.app.core.ui.NuvioActionLabel +import com.nuvio.app.core.ui.PosterCardStyleRepository +import com.nuvio.app.core.ui.PosterCardStyleUiState + +internal fun LazyListScope.posterCustomizationSettingsContent( + isTablet: Boolean, + uiState: PosterCardStyleUiState, +) { + item { + SettingsSection( + title = "POSTER CARD STYLE", + isTablet = isTablet, + actions = { + NuvioActionLabel( + text = "Reset", + onClick = PosterCardStyleRepository::resetToDefaults, + ) + }, + ) { + SettingsGroup(isTablet = isTablet) { + PosterCardStyleControls( + isTablet = isTablet, + widthDp = uiState.widthDp, + cornerRadiusDp = uiState.cornerRadiusDp, + onWidthSelected = PosterCardStyleRepository::setWidthDp, + onCornerRadiusSelected = PosterCardStyleRepository::setCornerRadiusDp, + ) + } + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun PosterCardStyleControls( + isTablet: Boolean, + widthDp: Int, + cornerRadiusDp: Int, + onWidthSelected: (Int) -> Unit, + onCornerRadiusSelected: (Int) -> Unit, +) { + val widthOptions = listOf( + PresetOption("Compact", 104), + PresetOption("Dense", 112), + PresetOption("Standard", 120), + PresetOption("Balanced", 126), + PresetOption("Comfort", 134), + PresetOption("Large", 140), + ) + val radiusOptions = listOf( + PresetOption("Sharp", 0), + PresetOption("Subtle", 4), + PresetOption("Classic", 8), + PresetOption("Rounded", 12), + PresetOption("Pill", 16), + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = if (isTablet) 20.dp else 16.dp, vertical = if (isTablet) 18.dp else 16.dp), + verticalArrangement = Arrangement.spacedBy(14.dp), + ) { + Text( + text = "Customize card width and corner radius for shared poster cards across the app.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + PosterCardLivePreview( + widthDp = widthDp, + cornerRadiusDp = cornerRadiusDp, + ) + PosterStyleOptionRow( + title = "Card Width", + selectedValue = widthDp, + options = widthOptions, + onSelected = onWidthSelected, + ) + PosterStyleOptionRow( + title = "Card Radius", + selectedValue = cornerRadiusDp, + options = radiusOptions, + onSelected = onCornerRadiusSelected, + ) + } +} + +@Composable +private fun PosterCardLivePreview( + widthDp: Int, + cornerRadiusDp: Int, +) { + val targetHeightDp = (widthDp * 3) / 2 + val previewFrameWidthDp = 140 + val previewFrameHeightDp = 210 + val animatedWidth = animateDpAsState( + targetValue = widthDp.dp, + animationSpec = tween(durationMillis = 280), + label = "posterPreviewWidth", + ) + val animatedHeight = animateDpAsState( + targetValue = targetHeightDp.dp, + animationSpec = tween(durationMillis = 280), + label = "posterPreviewHeight", + ) + val animatedCornerRadius = animateDpAsState( + targetValue = cornerRadiusDp.dp, + animationSpec = tween(durationMillis = 220), + label = "posterPreviewCornerRadius", + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = "Live Preview", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.SemiBold, + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalAlignment = Alignment.Top, + ) { + Box( + modifier = Modifier + .width(previewFrameWidthDp.dp) + .height(previewFrameHeightDp.dp), + ) { + Box( + modifier = Modifier + .width(animatedWidth.value) + .height(animatedHeight.value) + .clip(RoundedCornerShape(animatedCornerRadius.value)) + .background(MaterialTheme.colorScheme.surfaceVariant), + ) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(10.dp) + .size(34.dp) + .clip(RoundedCornerShape(999.dp)) + .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.2f)), + ) + Box( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(10.dp) + .width(70.dp) + .height(7.dp) + .clip(RoundedCornerShape(999.dp)) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.22f)), + ) + } + } + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = "Width: ${widthDp}dp", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = "Corner radius: ${cornerRadiusDp}dp", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = "Height: ${targetHeightDp}dp", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .background(MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)), + ) + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun PosterStyleOptionRow( + title: String, + selectedValue: Int, + options: List, + onSelected: (Int) -> Unit, +) { + val selectedLabel = options.firstOrNull { it.value == selectedValue }?.label ?: "Custom" + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = "$title ($selectedLabel)", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.SemiBold, + ) + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + options.forEach { option -> + FilterChip( + selected = option.value == selectedValue, + onClick = { onSelected(option.value) }, + label = { Text(option.label) }, + colors = FilterChipDefaults.filterChipColors( + selectedContainerColor = MaterialTheme.colorScheme.primaryContainer, + selectedLabelColor = MaterialTheme.colorScheme.onPrimaryContainer, + ), + ) + } + } + } +} + +private data class PresetOption( + val label: String, + val value: Int, +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt index 2e71a4ba..8b0f5c15 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt @@ -49,6 +49,11 @@ internal enum class SettingsPage( category = SettingsCategory.General, parentPage = Appearance, ), + PosterCustomization( + title = "Poster Customization", + category = SettingsCategory.General, + parentPage = Appearance, + ), ContentDiscovery( title = "Content & Discovery", category = SettingsCategory.General, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt index f5dfad2d..fd861d66 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt @@ -42,6 +42,8 @@ import com.nuvio.app.core.ui.PlatformBackHandler import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.details.MetaScreenSettingsRepository import com.nuvio.app.features.details.MetaScreenSettingsUiState +import com.nuvio.app.core.ui.PosterCardStyleRepository +import com.nuvio.app.core.ui.PosterCardStyleUiState import com.nuvio.app.features.home.HomeCatalogSettingsItem import com.nuvio.app.features.home.HomeCatalogSettingsRepository import com.nuvio.app.features.mdblist.MdbListSettings @@ -129,6 +131,10 @@ fun SettingsScreen( ContinueWatchingPreferencesRepository.ensureLoaded() ContinueWatchingPreferencesRepository.uiState }.collectAsStateWithLifecycle() + val posterCardStyleUiState by remember { + PosterCardStyleRepository.ensureLoaded() + PosterCardStyleRepository.uiState + }.collectAsStateWithLifecycle() val episodeReleaseNotificationsUiState by remember { EpisodeReleaseNotificationsRepository.ensureLoaded() EpisodeReleaseNotificationsRepository.uiState @@ -179,6 +185,7 @@ fun SettingsScreen( homescreenItems = homescreenSettingsUiState.items, metaScreenSettingsUiState = metaScreenSettingsUiState, continueWatchingPreferencesUiState = continueWatchingPreferencesUiState, + posterCardStyleUiState = posterCardStyleUiState, onSwitchProfile = onSwitchProfile, onDownloadsClick = onDownloadsClick, onCollectionsClick = onCollectionsClick, @@ -214,6 +221,7 @@ fun SettingsScreen( homescreenItems = homescreenSettingsUiState.items, metaScreenSettingsUiState = metaScreenSettingsUiState, continueWatchingPreferencesUiState = continueWatchingPreferencesUiState, + posterCardStyleUiState = posterCardStyleUiState, onSwitchProfile = onSwitchProfile, onHomescreenClick = onHomescreenClick, onMetaScreenClick = onMetaScreenClick, @@ -259,6 +267,7 @@ private fun MobileSettingsScreen( homescreenItems: List, metaScreenSettingsUiState: MetaScreenSettingsUiState, continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState, + posterCardStyleUiState: PosterCardStyleUiState, onSwitchProfile: (() -> Unit)? = null, onHomescreenClick: () -> Unit = {}, onMetaScreenClick: () -> Unit = {}, @@ -318,6 +327,7 @@ private fun MobileSettingsScreen( amoledEnabled = amoledEnabled, onAmoledToggle = onAmoledToggle, onContinueWatchingClick = onContinueWatchingClick, + onPosterCustomizationClick = { onPageChange(SettingsPage.PosterCustomization) }, ) SettingsPage.Notifications -> notificationsSettingsContent( isTablet = false, @@ -330,6 +340,10 @@ private fun MobileSettingsScreen( upNextFromFurthestEpisode = continueWatchingPreferencesUiState.upNextFromFurthestEpisode, showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch, ) + SettingsPage.PosterCustomization -> posterCustomizationSettingsContent( + isTablet = false, + uiState = posterCardStyleUiState, + ) SettingsPage.ContentDiscovery -> contentDiscoveryContent( isTablet = false, showPluginsEntry = AppFeaturePolicy.pluginsEnabled, @@ -404,6 +418,7 @@ private fun TabletSettingsScreen( homescreenItems: List, metaScreenSettingsUiState: MetaScreenSettingsUiState, continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState, + posterCardStyleUiState: PosterCardStyleUiState, onSwitchProfile: (() -> Unit)? = null, onDownloadsClick: () -> Unit = {}, onCollectionsClick: () -> Unit = {}, @@ -526,6 +541,7 @@ private fun TabletSettingsScreen( amoledEnabled = amoledEnabled, onAmoledToggle = onAmoledToggle, onContinueWatchingClick = { openInlinePage(SettingsPage.ContinueWatching) }, + onPosterCustomizationClick = { openInlinePage(SettingsPage.PosterCustomization) }, ) SettingsPage.Notifications -> notificationsSettingsContent( isTablet = true, @@ -538,6 +554,10 @@ private fun TabletSettingsScreen( upNextFromFurthestEpisode = continueWatchingPreferencesUiState.upNextFromFurthestEpisode, showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch, ) + SettingsPage.PosterCustomization -> posterCustomizationSettingsContent( + isTablet = true, + uiState = posterCardStyleUiState, + ) SettingsPage.ContentDiscovery -> contentDiscoveryContent( isTablet = true, showPluginsEntry = AppFeaturePolicy.pluginsEnabled, diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt index 921ba2d8..f46b85e6 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt @@ -14,6 +14,7 @@ internal actual object PlatformLocalAccountDataCleaner { private val profileScopedBaseKeys = listOf( "catalog_settings_payload", "continue_watching_preferences_payload", + "poster_card_style_payload", "episode_release_notifications_payload", "episode_release_notification_scheduled_ids", "selected_theme", diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.ios.kt new file mode 100644 index 00000000..67a5083d --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PosterCardStyleStorage.ios.kt @@ -0,0 +1,15 @@ +package com.nuvio.app.core.ui + +import com.nuvio.app.core.storage.ProfileScopedKey +import platform.Foundation.NSUserDefaults + +actual object PosterCardStyleStorage { + private const val payloadKey = "poster_card_style_payload" + + actual fun loadPayload(): String? = + NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(payloadKey)) + + actual fun savePayload(payload: String) { + NSUserDefaults.standardUserDefaults.setObject(payload, forKey = ProfileScopedKey.of(payloadKey)) + } +} \ No newline at end of file