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