diff --git a/composeApp/src/androidFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt b/composeApp/src/androidFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt index 62e4aa331..c09e2c295 100644 --- a/composeApp/src/androidFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt +++ b/composeApp/src/androidFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt @@ -2,6 +2,9 @@ package com.nuvio.app.core.build actual object AppFeaturePolicy { actual val pluginsEnabled: Boolean = true + actual val supportersContributorsPageEnabled: Boolean = true + actual val accountDeletionEnabled: Boolean = false + actual val personalMediaAddonCopyEnabled: Boolean = false actual val p2pEnabled: Boolean = true actual val trailerPlaybackMode: TrailerPlaybackMode = TrailerPlaybackMode.IN_APP actual val heroTrailerPlaybackSupported: Boolean = true diff --git a/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt index 1a639c5cc..3be7ac2f0 100644 --- a/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt +++ b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.android.kt @@ -2,6 +2,9 @@ package com.nuvio.app.core.build actual object AppFeaturePolicy { actual val pluginsEnabled: Boolean = false + actual val supportersContributorsPageEnabled: Boolean = true + actual val accountDeletionEnabled: Boolean = false + actual val personalMediaAddonCopyEnabled: Boolean = false actual val p2pEnabled: Boolean = true actual val trailerPlaybackMode: TrailerPlaybackMode = TrailerPlaybackMode.EXTERNAL actual val heroTrailerPlaybackSupported: Boolean = false diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 008521a52..ff7e6c75b 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -32,6 +32,11 @@ Unavailable Configure addon Delete addon + Connect your own media server to browse and play from your personal library. + Add a server URL above when you want Nuvio to show your private library. + No personal libraries connected. + Server URL + Add Add a manifest URL to start loading catalogs, metadata, streams or subtitles into Nuvio. No addons installed yet. Enter an addon URL. @@ -634,6 +639,7 @@ HOME SOURCES Install, remove, refresh, and sort your content sources. + Connect personal media sources and manage access to your own library. Install JavaScript scraper repositories and test providers internally. Adjust home layout, content visibility, and poster behavior Settings for the detail and episode screens. diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 8ac3a7b50..4f040a0be 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -1582,7 +1582,9 @@ private fun MainAppContent( }, onAccountSettingsClick = { navController.navigate(AccountSettingsRoute) }, onSupportersContributorsSettingsClick = { - navController.navigate(SupportersContributorsSettingsRoute) + if (AppFeaturePolicy.supportersContributorsPageEnabled) { + navController.navigate(SupportersContributorsSettingsRoute) + } }, onLicensesAttributionsSettingsClick = { navController.navigate(LicensesAttributionsSettingsRoute) @@ -2681,9 +2683,15 @@ private fun MainAppContent( navController = navController, backStackEntry = backStackEntry, ) - SupportersContributorsSettingsScreen( - onBack = onBack, - ) + if (AppFeaturePolicy.supportersContributorsPageEnabled) { + SupportersContributorsSettingsScreen( + onBack = onBack, + ) + } else { + LaunchedEffect(Unit) { + onBack() + } + } } composable { backStackEntry -> val onBack = rememberGuardedPopBackStack( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.kt index 55f7c1e16..3c3f74700 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.kt @@ -7,6 +7,9 @@ enum class TrailerPlaybackMode { expect object AppFeaturePolicy { val pluginsEnabled: Boolean + val supportersContributorsPageEnabled: Boolean + val accountDeletionEnabled: Boolean + val personalMediaAddonCopyEnabled: Boolean val p2pEnabled: Boolean val trailerPlaybackMode: TrailerPlaybackMode val heroTrailerPlaybackSupported: Boolean diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonsScreen.kt index 7e12666dc..2d7db4a85 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonsScreen.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.nuvio.app.core.build.AppFeaturePolicy import coil3.compose.AsyncImage import com.nuvio.app.core.ui.NuvioIconActionButton import com.nuvio.app.core.ui.NuvioInfoBadge @@ -93,6 +94,7 @@ internal fun AddonsSettingsPageContent( var formMessage by rememberSaveable { mutableStateOf(null) } var installModalState by remember { mutableStateOf(null) } val enterAddonUrlMessage = stringResource(Res.string.addons_error_enter_url) + val usePersonalMediaCopy = AppFeaturePolicy.personalMediaAddonCopyEnabled val overview = remember(uiState.addons) { uiState.addons.toOverview() } @@ -107,6 +109,7 @@ internal fun AddonsSettingsPageContent( AddAddonCard( addonUrl = addonUrl, formMessage = formMessage, + usePersonalMediaCopy = usePersonalMediaCopy, onAddonUrlChange = { addonUrl = it formMessage = null @@ -138,7 +141,7 @@ internal fun AddonsSettingsPageContent( SectionHeader(stringResource(Res.string.addons_section_installed)) if (uiState.addons.isEmpty()) { - EmptyStateCard() + EmptyStateCard(usePersonalMediaCopy = usePersonalMediaCopy) } else { val lastIndex = uiState.addons.lastIndex uiState.addons.forEachIndexed { index, addon -> @@ -283,6 +286,7 @@ private fun VerticalSeparator() { private fun AddAddonCard( addonUrl: String, formMessage: String?, + usePersonalMediaCopy: Boolean, onAddonUrlChange: (String) -> Unit, onAddClick: () -> Unit, ) { @@ -290,14 +294,34 @@ private fun AddAddonCard( NuvioInputField( value = addonUrl, onValueChange = onAddonUrlChange, - placeholder = stringResource(Res.string.addons_input_placeholder), + placeholder = stringResource( + if (usePersonalMediaCopy) { + Res.string.addons_appstore_input_placeholder + } else { + Res.string.addons_input_placeholder + }, + ), ) Spacer(modifier = Modifier.height(18.dp)) NuvioPrimaryButton( - text = stringResource(Res.string.addons_install_button), + text = stringResource( + if (usePersonalMediaCopy) { + Res.string.addons_appstore_install_button + } else { + Res.string.addons_install_button + }, + ), enabled = addonUrl.isNotBlank(), onClick = onAddClick, ) + if (usePersonalMediaCopy) { + Spacer(modifier = Modifier.height(14.dp)) + Text( + text = stringResource(Res.string.addons_appstore_add_description), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } formMessage?.let { message -> Spacer(modifier = Modifier.height(14.dp)) Text( @@ -330,16 +354,30 @@ private sealed interface AddonInstallModalState { } @Composable -private fun EmptyStateCard() { +private fun EmptyStateCard( + usePersonalMediaCopy: Boolean, +) { NuvioSurfaceCard { Text( - text = stringResource(Res.string.addons_empty_title), + text = stringResource( + if (usePersonalMediaCopy) { + Res.string.addons_appstore_empty_title + } else { + Res.string.addons_empty_title + }, + ), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onSurface, ) Spacer(modifier = Modifier.height(8.dp)) Text( - text = stringResource(Res.string.addons_empty_subtitle), + text = stringResource( + if (usePersonalMediaCopy) { + Res.string.addons_appstore_empty_subtitle + } else { + Res.string.addons_empty_subtitle + }, + ), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AccountSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AccountSettingsPage.kt index 9c875a37c..7cc47203c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AccountSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AccountSettingsPage.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -25,16 +27,24 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.nuvio.app.core.auth.AuthRepository import com.nuvio.app.core.auth.AuthState +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.core.network.SyncBackendRepository import com.nuvio.app.core.ui.NuvioPrimaryButton import com.nuvio.app.core.ui.NuvioStatusModal import com.nuvio.app.core.ui.NuvioSurfaceCard +import com.nuvio.app.core.ui.NuvioTokens +import com.nuvio.app.core.ui.nuvio import com.nuvio.app.features.dev.DebugSyncBackendSwitch import com.nuvio.app.features.dev.shouldShowDebugSyncBackendSwitch import kotlinx.coroutines.launch import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.action_cancel import nuvio.composeapp.generated.resources.compose_settings_page_account +import nuvio.composeapp.generated.resources.auth_account_deletion_failed +import nuvio.composeapp.generated.resources.settings_account_delete_account +import nuvio.composeapp.generated.resources.settings_account_delete_account_description +import nuvio.composeapp.generated.resources.settings_account_delete_confirm_message +import nuvio.composeapp.generated.resources.settings_account_delete_confirm_title import nuvio.composeapp.generated.resources.settings_account_email import nuvio.composeapp.generated.resources.settings_account_not_signed_in import nuvio.composeapp.generated.resources.settings_account_sign_out @@ -62,7 +72,12 @@ private fun AccountSettingsBody( val syncBackendState by SyncBackendRepository.state.collectAsStateWithLifecycle() val scope = rememberCoroutineScope() var showSignOutConfirm by remember { mutableStateOf(false) } + var showDeleteConfirm by remember { mutableStateOf(false) } + var isDeletingAccount by remember { mutableStateOf(false) } + var deleteErrorMessage by remember { mutableStateOf(null) } val syncBackendLabel = syncBackendState.selectedBackend.displayName + val deleteAccountFallbackMessage = stringResource(Res.string.auth_account_deletion_failed) + val canDeleteAccount = AppFeaturePolicy.accountDeletionEnabled && authState is AuthState.Authenticated Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { NuvioSurfaceCard { @@ -121,6 +136,16 @@ private fun AccountSettingsBody( text = stringResource(Res.string.settings_account_sign_out), onClick = { showSignOutConfirm = true }, ) + + if (canDeleteAccount) { + DeleteAccountCard( + errorMessage = deleteErrorMessage, + onDeleteClick = { + deleteErrorMessage = null + showDeleteConfirm = true + }, + ) + } } NuvioStatusModal( @@ -135,6 +160,85 @@ private fun AccountSettingsBody( }, onDismiss = { showSignOutConfirm = false }, ) + + NuvioStatusModal( + title = stringResource(Res.string.settings_account_delete_confirm_title), + message = stringResource(Res.string.settings_account_delete_confirm_message), + isVisible = showDeleteConfirm, + isBusy = isDeletingAccount, + confirmText = stringResource(Res.string.settings_account_delete_account), + dismissText = stringResource(Res.string.action_cancel), + onConfirm = { + if (isDeletingAccount) return@NuvioStatusModal + isDeletingAccount = true + scope.launch { + val result = AuthRepository.deleteAccount() + isDeletingAccount = false + showDeleteConfirm = false + deleteErrorMessage = if (result.isSuccess) { + null + } else { + AuthRepository.error.value + ?: result.exceptionOrNull()?.message + ?: deleteAccountFallbackMessage + } + } + }, + onDismiss = { + if (!isDeletingAccount) { + showDeleteConfirm = false + } + }, + ) +} + +@Composable +private fun DeleteAccountCard( + errorMessage: String?, + onDeleteClick: () -> Unit, +) { + val tokens = MaterialTheme.nuvio + + NuvioSurfaceCard { + Text( + text = stringResource(Res.string.settings_account_delete_account), + style = MaterialTheme.typography.titleMedium, + color = tokens.colors.textPrimary, + fontWeight = FontWeight.SemiBold, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(Res.string.settings_account_delete_account_description), + style = MaterialTheme.typography.bodyMedium, + color = tokens.colors.textMuted, + ) + errorMessage?.let { message -> + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = message, + style = MaterialTheme.typography.bodySmall, + color = tokens.colors.danger, + ) + } + Spacer(modifier = Modifier.height(14.dp)) + Button( + onClick = onDeleteClick, + modifier = Modifier + .fillMaxWidth() + .height(NuvioTokens.Space.s48 + NuvioTokens.Space.s4), + shape = tokens.shapes.button, + colors = ButtonDefaults.buttonColors( + containerColor = tokens.colors.danger, + contentColor = tokens.colors.textInverse, + ), + ) { + Text( + text = stringResource(Res.string.settings_account_delete_account), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center, + ) + } + } } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContentDiscoverySettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContentDiscoverySettingsPage.kt index 438fd3c30..bd2652ca0 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContentDiscoverySettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContentDiscoverySettingsPage.kt @@ -7,6 +7,7 @@ import androidx.compose.material.icons.rounded.Extension import androidx.compose.material.icons.rounded.Home import androidx.compose.material.icons.rounded.Hub import androidx.compose.material.icons.rounded.Tune +import com.nuvio.app.core.build.AppFeaturePolicy import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.compose_settings_page_addons import nuvio.composeapp.generated.resources.compose_settings_page_homescreen @@ -14,6 +15,7 @@ import nuvio.composeapp.generated.resources.compose_settings_page_meta_screen import nuvio.composeapp.generated.resources.compose_settings_page_plugins import nuvio.composeapp.generated.resources.collections_header import nuvio.composeapp.generated.resources.settings_content_discovery_addons_description +import nuvio.composeapp.generated.resources.settings_content_discovery_addons_description_appstore import nuvio.composeapp.generated.resources.settings_content_discovery_collections_description import nuvio.composeapp.generated.resources.settings_content_discovery_homescreen_description import nuvio.composeapp.generated.resources.settings_content_discovery_meta_screen_description @@ -39,7 +41,13 @@ internal fun LazyListScope.contentDiscoveryContent( SettingsGroup(isTablet = isTablet) { SettingsNavigationRow( title = stringResource(Res.string.compose_settings_page_addons), - description = stringResource(Res.string.settings_content_discovery_addons_description), + description = stringResource( + if (AppFeaturePolicy.personalMediaAddonCopyEnabled) { + Res.string.settings_content_discovery_addons_description_appstore + } else { + Res.string.settings_content_discovery_addons_description + }, + ), icon = Icons.Rounded.Extension, isTablet = isTablet, onClick = onAddonsClick, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsRootPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsRootPage.kt index f557f762b..36f04b12b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsRootPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsRootPage.kt @@ -79,6 +79,7 @@ internal fun LazyListScope.settingsRootContent( showGeneralSection: Boolean = true, showAboutSection: Boolean = true, showAdvancedSection: Boolean = true, + showSupportersContributorsPage: Boolean = true, ) { if (showAccountSection) { item { @@ -189,14 +190,16 @@ internal fun LazyListScope.settingsRootContent( isTablet = isTablet, ) { SettingsGroup(isTablet = isTablet) { - SettingsNavigationRow( - title = stringResource(Res.string.compose_settings_page_supporters_contributors), - description = stringResource(Res.string.about_supporters_contributors_subtitle), - icon = Icons.Rounded.Favorite, - isTablet = isTablet, - onClick = onSupportersContributorsClick, - ) - SettingsGroupDivider(isTablet = isTablet) + if (showSupportersContributorsPage) { + SettingsNavigationRow( + title = stringResource(Res.string.compose_settings_page_supporters_contributors), + description = stringResource(Res.string.about_supporters_contributors_subtitle), + icon = Icons.Rounded.Favorite, + isTablet = isTablet, + onClick = onSupportersContributorsClick, + ) + SettingsGroupDivider(isTablet = isTablet) + } SettingsNavigationRow( title = stringResource(Res.string.compose_settings_page_licenses_attributions), description = stringResource(Res.string.about_licenses_attributions_subtitle), 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 ea95d9ffd..fab7e7ed3 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 @@ -94,6 +94,12 @@ private val SettingsSearchRevealThreshold = 28.dp private const val SettingsSearchRevealAnimationMillis = 240L private const val SettingsSearchRevealHapticDelayMillis = 90L +private fun SettingsPage.isEnabledByPolicy(): Boolean = + when (this) { + SettingsPage.SupportersContributors -> AppFeaturePolicy.supportersContributorsPageEnabled + else -> true + } + @Composable fun SettingsScreen( modifier: Modifier = Modifier, @@ -216,9 +222,20 @@ fun SettingsScreen( var currentPage by rememberSaveable { mutableStateOf(SettingsPage.Root.name) } val scrollToTopRequests = remember { MutableSharedFlow(extraBufferCapacity = 1) } - val page = remember(currentPage) { SettingsPage.valueOf(currentPage) } + val page = remember(currentPage) { + runCatching { SettingsPage.valueOf(currentPage) } + .getOrDefault(SettingsPage.Root) + .takeIf { it.isEnabledByPolicy() } + ?: SettingsPage.Root + } val previousPage = page.previousPage() + LaunchedEffect(page, currentPage) { + if (page.name != currentPage) { + currentPage = page.name + } + } + LaunchedEffect(rootActionRequests, rootActionsEnabled, page) { rootActionRequests.collect { if (!rootActionsEnabled) return@collect @@ -232,9 +249,12 @@ fun SettingsScreen( } LaunchedEffect(requestedPageName, rootActionsEnabled) { - val targetPage = requestedPageName - ?.let { runCatching { SettingsPage.valueOf(it) }.getOrNull() } - ?: return@LaunchedEffect + val requestedPage = requestedPageName ?: return@LaunchedEffect + val targetPage = runCatching { SettingsPage.valueOf(requestedPage) }.getOrNull() + if (targetPage == null || !targetPage.isEnabledByPolicy()) { + onRequestedPageConsumed() + return@LaunchedEffect + } if (!rootActionsEnabled) return@LaunchedEffect currentPage = targetPage.name onRequestedPageConsumed() @@ -449,6 +469,9 @@ private fun MobileSettingsScreen( } val searchEntries = settingsSearchEntries( pluginsEnabled = AppFeaturePolicy.pluginsEnabled, + supportersContributorsPageEnabled = AppFeaturePolicy.supportersContributorsPageEnabled, + accountDeletionEnabled = AppFeaturePolicy.accountDeletionEnabled, + personalMediaAddonCopyEnabled = AppFeaturePolicy.personalMediaAddonCopyEnabled, liquidGlassNativeTabBarSupported = liquidGlassNativeTabBarSupported, switchProfileAvailable = onSwitchProfile != null, checkForUpdatesAvailable = onCheckForUpdatesClick != null, @@ -458,7 +481,11 @@ private fun MobileSettingsScreen( when (target) { is SettingsSearchTarget.Page -> when (target.page) { SettingsPage.Account -> onAccountClick() - SettingsPage.SupportersContributors -> onSupportersContributorsClick() + SettingsPage.SupportersContributors -> { + if (AppFeaturePolicy.supportersContributorsPageEnabled) { + onSupportersContributorsClick() + } + } SettingsPage.LicensesAttributions -> onLicensesAttributionsClick() SettingsPage.ContinueWatching -> onContinueWatchingClick() SettingsPage.Addons -> onAddonsClick() @@ -531,15 +558,18 @@ private fun MobileSettingsScreen( onDownloadsClick = onDownloadsClick, onAccountClick = onAccountClick, onSwitchProfileClick = onSwitchProfile, + showSupportersContributorsPage = AppFeaturePolicy.supportersContributorsPageEnabled, ) } } SettingsPage.Account -> accountSettingsContent( isTablet = false, ) - SettingsPage.SupportersContributors -> supportersContributorsContent( - isTablet = false, - ) + SettingsPage.SupportersContributors -> { + if (AppFeaturePolicy.supportersContributorsPageEnabled) { + supportersContributorsContent(isTablet = false) + } + } SettingsPage.LicensesAttributions -> licensesAttributionsContent( isTablet = false, ) @@ -822,6 +852,9 @@ private fun TabletSettingsScreen( val hapticScope = rememberCoroutineScope() val searchEntries = settingsSearchEntries( pluginsEnabled = AppFeaturePolicy.pluginsEnabled, + supportersContributorsPageEnabled = AppFeaturePolicy.supportersContributorsPageEnabled, + accountDeletionEnabled = AppFeaturePolicy.accountDeletionEnabled, + personalMediaAddonCopyEnabled = AppFeaturePolicy.personalMediaAddonCopyEnabled, liquidGlassNativeTabBarSupported = liquidGlassNativeTabBarSupported, switchProfileAvailable = onSwitchProfile != null, checkForUpdatesAvailable = onCheckForUpdatesClick != null, @@ -829,7 +862,11 @@ private fun TabletSettingsScreen( fun openSearchTarget(target: SettingsSearchTarget) { when (target) { - is SettingsSearchTarget.Page -> openInlinePage(target.page) + is SettingsSearchTarget.Page -> { + if (target.page.isEnabledByPolicy()) { + openInlinePage(target.page) + } + } SettingsSearchTarget.Downloads -> onDownloadsClick() SettingsSearchTarget.Collections -> onCollectionsClick() SettingsSearchTarget.SwitchProfile -> onSwitchProfile?.invoke() @@ -924,15 +961,18 @@ private fun TabletSettingsScreen( showGeneralSection = activeCategory == SettingsCategory.General, showAboutSection = activeCategory == SettingsCategory.About, showAdvancedSection = activeCategory == SettingsCategory.Advanced, + showSupportersContributorsPage = AppFeaturePolicy.supportersContributorsPageEnabled, ) } } SettingsPage.Account -> accountSettingsContent( isTablet = true, ) - SettingsPage.SupportersContributors -> supportersContributorsContent( - isTablet = true, - ) + SettingsPage.SupportersContributors -> { + if (AppFeaturePolicy.supportersContributorsPageEnabled) { + supportersContributorsContent(isTablet = true) + } + } SettingsPage.LicensesAttributions -> licensesAttributionsContent( isTablet = true, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt index 248d63977..69ab6a9af 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt @@ -79,6 +79,9 @@ internal data class SettingsSearchEntry( @Composable internal fun settingsSearchEntries( pluginsEnabled: Boolean, + supportersContributorsPageEnabled: Boolean, + accountDeletionEnabled: Boolean, + personalMediaAddonCopyEnabled: Boolean, liquidGlassNativeTabBarSupported: Boolean, switchProfileAvailable: Boolean, checkForUpdatesAvailable: Boolean, @@ -261,14 +264,16 @@ internal fun settingsSearchEntries( description = stringResource(Res.string.compose_settings_root_notifications_description), icon = Icons.Rounded.Notifications, ) - addPage( - page = SettingsPage.SupportersContributors, - key = "supporters", - title = supportersPage, - description = stringResource(Res.string.about_supporters_contributors_subtitle), - category = aboutCategory, - icon = Icons.Rounded.Favorite, - ) + if (supportersContributorsPageEnabled) { + addPage( + page = SettingsPage.SupportersContributors, + key = "supporters", + title = supportersPage, + description = stringResource(Res.string.about_supporters_contributors_subtitle), + category = aboutCategory, + icon = Icons.Rounded.Favorite, + ) + } addPage( page = SettingsPage.LicensesAttributions, key = "licenses-attributions", @@ -316,7 +321,7 @@ internal fun settingsSearchEntries( key = "check-updates", title = stringResource(Res.string.compose_settings_root_check_updates_title), description = stringResource(Res.string.compose_settings_root_check_updates_description), - page = supportersPage, + page = if (supportersContributorsPageEnabled) supportersPage else licensesPage, section = stringResource(Res.string.compose_settings_root_about_section), category = aboutCategory, icon = Icons.Rounded.CloudDownload, @@ -342,6 +347,18 @@ internal fun settingsSearchEntries( category = accountCategory, icon = Icons.Rounded.AccountCircle, ) + if (accountDeletionEnabled) { + addRow( + page = SettingsPage.Account, + key = "account-delete", + title = stringResource(Res.string.settings_account_delete_account), + description = stringResource(Res.string.settings_account_delete_account_description), + pageLabel = accountPage, + section = accountPage, + category = accountCategory, + icon = Icons.Rounded.AccountCircle, + ) + } addRow( page = SettingsPage.Appearance, @@ -418,7 +435,13 @@ internal fun settingsSearchEntries( page = SettingsPage.Addons, key = "addons", title = addonsPage, - description = stringResource(Res.string.settings_content_discovery_addons_description), + description = stringResource( + if (personalMediaAddonCopyEnabled) { + Res.string.settings_content_discovery_addons_description_appstore + } else { + Res.string.settings_content_discovery_addons_description + }, + ), icon = Icons.Rounded.Extension, ) if (pluginsEnabled) { diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.desktop.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.desktop.kt index ebffde7e9..9d23979f3 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.desktop.kt @@ -2,6 +2,9 @@ package com.nuvio.app.core.build actual object AppFeaturePolicy { actual val pluginsEnabled: Boolean = false + actual val supportersContributorsPageEnabled: Boolean = true + actual val accountDeletionEnabled: Boolean = false + actual val personalMediaAddonCopyEnabled: Boolean = false actual val p2pEnabled: Boolean = false actual val trailerPlaybackMode: TrailerPlaybackMode = TrailerPlaybackMode.EXTERNAL actual val heroTrailerPlaybackSupported: Boolean = false diff --git a/composeApp/src/iosAppStore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt b/composeApp/src/iosAppStore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt index 37660c9c2..20a1f5522 100644 --- a/composeApp/src/iosAppStore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt +++ b/composeApp/src/iosAppStore/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt @@ -2,6 +2,9 @@ package com.nuvio.app.core.build actual object AppFeaturePolicy { actual val pluginsEnabled: Boolean = false + actual val supportersContributorsPageEnabled: Boolean = false + actual val accountDeletionEnabled: Boolean = true + actual val personalMediaAddonCopyEnabled: Boolean = true actual val p2pEnabled: Boolean = false actual val trailerPlaybackMode: TrailerPlaybackMode = TrailerPlaybackMode.EXTERNAL actual val heroTrailerPlaybackSupported: Boolean = false diff --git a/composeApp/src/iosFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt b/composeApp/src/iosFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt index 2a17ddfd5..dc3e1839f 100644 --- a/composeApp/src/iosFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt +++ b/composeApp/src/iosFull/kotlin/com/nuvio/app/core/build/AppFeaturePolicy.ios.kt @@ -2,6 +2,9 @@ package com.nuvio.app.core.build actual object AppFeaturePolicy { actual val pluginsEnabled: Boolean = true + actual val supportersContributorsPageEnabled: Boolean = true + actual val accountDeletionEnabled: Boolean = false + actual val personalMediaAddonCopyEnabled: Boolean = false actual val p2pEnabled: Boolean = false actual val trailerPlaybackMode: TrailerPlaybackMode = TrailerPlaybackMode.IN_APP actual val heroTrailerPlaybackSupported: Boolean = false