From f65f934acd3fe8a3f80913f92cecee7bb204fdd8 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Sat, 4 Apr 2026 21:30:19 +0530 Subject: [PATCH] flavouring yt extraction --- composeApp/build.gradle.kts | 9 ++ ...atformPlaybackDataSourceFactory.android.kt | 9 ++ .../features/trailer/InAppYouTubeExtractor.kt | 0 .../TrailerPlaybackResolver.android.kt | 0 .../YoutubeChunkedDataSourceFactory.kt | 0 .../features/player/PlayerEngine.android.kt | 3 +- ...atformPlaybackDataSourceFactory.android.kt | 9 ++ .../TrailerPlaybackResolver.android.kt | 5 + .../commonMain/kotlin/com/nuvio/app/App.kt | 17 +++- .../core/storage/LocalAccountDataCleaner.kt | 5 +- .../com/nuvio/app/core/sync/SyncManager.kt | 11 ++- .../app/features/details/MetaDetailsScreen.kt | 95 +++++++++++-------- .../player/PlayerSettingsRepository.kt | 34 +++++-- .../player/PlayerStreamsRepository.kt | 9 +- .../features/profiles/ProfileRepository.kt | 4 +- .../settings/ContentDiscoverySettingsPage.kt | 17 ++-- .../features/settings/PlaybackSettingsPage.kt | 52 ++++++++-- .../settings/SettingsFullScreenPages.kt | 6 ++ .../app/features/settings/SettingsScreen.kt | 8 +- .../app/features/streams/StreamsRepository.kt | 16 +++- 20 files changed, 225 insertions(+), 84 deletions(-) create mode 100644 composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt rename composeApp/src/{androidMain => androidFull}/kotlin/com/nuvio/app/features/trailer/InAppYouTubeExtractor.kt (100%) rename composeApp/src/{androidMain => androidFull}/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt (100%) rename composeApp/src/{androidMain => androidFull}/kotlin/com/nuvio/app/features/trailer/YoutubeChunkedDataSourceFactory.kt (100%) create mode 100644 composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt create mode 100644 composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 1393d610..98c120b9 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -210,6 +210,15 @@ android { versionCode = 1 versionName = "1.0" } + flavorDimensions += "distribution" + productFlavors { + create("full") { + dimension = "distribution" + } + create("playstore") { + dimension = "distribution" + } + } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" diff --git a/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt new file mode 100644 index 00000000..e56c9c2c --- /dev/null +++ b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt @@ -0,0 +1,9 @@ +package com.nuvio.app.features.player + +import androidx.media3.datasource.DataSource +import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory + +internal object PlatformPlaybackDataSourceFactory { + fun create(defaultRequestHeaders: Map): DataSource.Factory = + YoutubeChunkedDataSourceFactory(defaultRequestHeaders = defaultRequestHeaders) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trailer/InAppYouTubeExtractor.kt b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/trailer/InAppYouTubeExtractor.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/com/nuvio/app/features/trailer/InAppYouTubeExtractor.kt rename to composeApp/src/androidFull/kotlin/com/nuvio/app/features/trailer/InAppYouTubeExtractor.kt diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt rename to composeApp/src/androidFull/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trailer/YoutubeChunkedDataSourceFactory.kt b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/trailer/YoutubeChunkedDataSourceFactory.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/com/nuvio/app/features/trailer/YoutubeChunkedDataSourceFactory.kt rename to composeApp/src/androidFull/kotlin/com/nuvio/app/features/trailer/YoutubeChunkedDataSourceFactory.kt diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt index ada92b37..c218f3a1 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt @@ -41,7 +41,6 @@ import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import androidx.media3.ui.SubtitleView import androidx.media3.ui.CaptionStyleCompat -import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory import kotlinx.coroutines.delay import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -112,7 +111,7 @@ actual fun PlatformPlayerSurface( .setTsExtractorTimestampSearchBytes(1500 * TsExtractor.TS_PACKET_SIZE) val mediaSourceFactory = DefaultMediaSourceFactory( - YoutubeChunkedDataSourceFactory(defaultRequestHeaders = sanitizedSourceHeaders), + PlatformPlaybackDataSourceFactory.create(defaultRequestHeaders = sanitizedSourceHeaders), extractorsFactory, ) diff --git a/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt new file mode 100644 index 00000000..a3896fc8 --- /dev/null +++ b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt @@ -0,0 +1,9 @@ +package com.nuvio.app.features.player + +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultHttpDataSource + +internal object PlatformPlaybackDataSourceFactory { + fun create(defaultRequestHeaders: Map): DataSource.Factory = + DefaultHttpDataSource.Factory().setDefaultRequestProperties(defaultRequestHeaders) +} \ No newline at end of file diff --git a/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt new file mode 100644 index 00000000..33c4aa29 --- /dev/null +++ b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/trailer/TrailerPlaybackResolver.android.kt @@ -0,0 +1,5 @@ +package com.nuvio.app.features.trailer + +actual object TrailerPlaybackResolver { + actual suspend fun resolveFromYouTubeUrl(youtubeUrl: String): TrailerPlaybackSource? = null +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index e10c81ee..6d4c1e7a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -66,6 +66,7 @@ import coil3.ImageLoader import coil3.compose.setSingletonImageLoaderFactory import coil3.request.CachePolicy import coil3.request.crossfade +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.core.auth.AuthRepository import com.nuvio.app.core.auth.AuthState import com.nuvio.app.core.deeplink.AppDeepLink @@ -609,7 +610,11 @@ private fun MainAppContent( onMetaScreenSettingsClick = { navController.navigate(MetaScreenSettingsRoute) }, onContinueWatchingSettingsClick = { navController.navigate(ContinueWatchingSettingsRoute) }, onAddonsSettingsClick = { navController.navigate(AddonsSettingsRoute) }, - onPluginsSettingsClick = { navController.navigate(PluginsSettingsRoute) }, + onPluginsSettingsClick = { + if (AppFeaturePolicy.pluginsEnabled) { + navController.navigate(PluginsSettingsRoute) + } + }, onAccountSettingsClick = { navController.navigate(AccountSettingsRoute) }, onInitialHomeContentRendered = { initialHomeReady = true }, ) @@ -1084,10 +1089,12 @@ private fun MainAppContent( onBack = { navController.popBackStack() }, ) } - composable { - PluginsSettingsScreen( - onBack = { navController.popBackStack() }, - ) + if (AppFeaturePolicy.pluginsEnabled) { + composable { + PluginsSettingsScreen( + onBack = { navController.popBackStack() }, + ) + } } composable { AccountSettingsScreen( 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 fab12c6f..3aa046ad 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 @@ -1,5 +1,6 @@ package com.nuvio.app.core.storage +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.catalog.CatalogRepository import com.nuvio.app.features.details.MetaDetailsRepository @@ -28,7 +29,9 @@ internal object LocalAccountDataCleaner { ProfileRepository.clearInMemory() AddonRepository.clearLocalState() - PluginRepository.clearLocalState() + if (AppFeaturePolicy.pluginsEnabled) { + PluginRepository.clearLocalState() + } HomeRepository.clear() HomeCatalogSettingsRepository.clearLocalState() MetaScreenSettingsRepository.clearLocalState() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt index 8d135d00..93c34b0d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt @@ -1,6 +1,7 @@ package com.nuvio.app.core.sync import co.touchlab.kermit.Logger +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.core.auth.AuthRepository import com.nuvio.app.core.auth.AuthState import com.nuvio.app.features.addons.AddonRepository @@ -31,10 +32,12 @@ object SyncManager { .onSuccess { log.i { "pullAllForProfile — addons pull completed" } } .onFailure { log.e(it) { "Addon pull failed" } } - log.i { "pullAllForProfile — pulling plugins (await)..." } - runCatching { PluginRepository.pullFromServer(profileId) } - .onSuccess { log.i { "pullAllForProfile — plugins pull completed" } } - .onFailure { log.e(it) { "Plugin pull failed" } } + if (AppFeaturePolicy.pluginsEnabled) { + log.i { "pullAllForProfile — pulling plugins (await)..." } + runCatching { PluginRepository.pullFromServer(profileId) } + .onSuccess { log.i { "pullAllForProfile — plugins pull completed" } } + .onFailure { log.e(it) { "Plugin pull failed" } } + } log.i { "pullAllForProfile — launching remaining pulls in parallel" } launch { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt index d6d7ebd2..1a03ec8c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt @@ -44,12 +44,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage +import com.nuvio.app.core.build.AppFeaturePolicy +import com.nuvio.app.core.build.TrailerPlaybackMode import com.nuvio.app.core.ui.NuvioBackButton import com.nuvio.app.core.ui.TraktListPickerDialog import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding @@ -295,37 +298,43 @@ fun MetaDetailsScreen( val hasTrailersSection = remember(meta) { meta.trailers.isNotEmpty() } + val uriHandler = LocalUriHandler.current + val inAppTrailerPlaybackEnabled = AppFeaturePolicy.trailerPlaybackMode == TrailerPlaybackMode.IN_APP val trailerScope = rememberCoroutineScope() var selectedTrailer by remember(meta.id) { mutableStateOf(null) } var trailerPlaybackSource by remember(meta.id) { mutableStateOf(null) } var trailerLoading by remember(meta.id) { mutableStateOf(false) } var trailerErrorMessage by remember(meta.id) { mutableStateOf(null) } var trailerRequestToken by remember(meta.id) { mutableIntStateOf(0) } - val resolveTrailer: (MetaTrailer) -> Unit = remember(meta.id) { + val resolveTrailer: (MetaTrailer) -> Unit = remember(meta.id, inAppTrailerPlaybackEnabled, uriHandler) { { trailer -> - selectedTrailer = trailer - trailerPlaybackSource = null - trailerErrorMessage = null - trailerLoading = true - trailerRequestToken += 1 - val currentRequestToken = trailerRequestToken - trailerScope.launch { - val youtubeUrl = trailer.key.takeIf { - it.startsWith("http://") || it.startsWith("https://") - } ?: "https://www.youtube.com/watch?v=${trailer.key}" - val resolvedSource = runCatching { - TrailerPlaybackResolver.resolveFromYouTubeUrl(youtubeUrl) - }.getOrNull() - if (currentRequestToken != trailerRequestToken) { - return@launch + val youtubeUrl = trailer.key.takeIf { + it.startsWith("http://") || it.startsWith("https://") + } ?: "https://www.youtube.com/watch?v=${trailer.key}" + if (!inAppTrailerPlaybackEnabled) { + runCatching { uriHandler.openUri(youtubeUrl) } + } else { + selectedTrailer = trailer + trailerPlaybackSource = null + trailerErrorMessage = null + trailerLoading = true + trailerRequestToken += 1 + val currentRequestToken = trailerRequestToken + trailerScope.launch { + val resolvedSource = runCatching { + TrailerPlaybackResolver.resolveFromYouTubeUrl(youtubeUrl) + }.getOrNull() + if (currentRequestToken != trailerRequestToken) { + return@launch + } + trailerPlaybackSource = resolvedSource + trailerErrorMessage = if (resolvedSource == null) { + "No playable trailer stream found." + } else { + null + } + trailerLoading = false } - trailerPlaybackSource = resolvedSource - trailerErrorMessage = if (resolvedSource == null) { - "No playable trailer stream found." - } else { - null - } - trailerLoading = false } } } @@ -654,25 +663,27 @@ fun MetaDetailsScreen( ) } - TrailerPlayerPopup( - visible = selectedTrailer != null, - trailerTitle = selectedTrailer?.displayName ?: selectedTrailer?.name.orEmpty(), - trailerType = selectedTrailer?.type.orEmpty(), - contentTitle = meta.name, - playbackSource = trailerPlaybackSource, - isLoading = trailerLoading, - errorMessage = trailerErrorMessage, - onDismiss = { - trailerRequestToken += 1 - trailerLoading = false - trailerPlaybackSource = null - trailerErrorMessage = null - selectedTrailer = null - }, - onRetry = selectedTrailer?.let { trailer -> - { resolveTrailer(trailer) } - }, - ) + if (inAppTrailerPlaybackEnabled) { + TrailerPlayerPopup( + visible = selectedTrailer != null, + trailerTitle = selectedTrailer?.displayName ?: selectedTrailer?.name.orEmpty(), + trailerType = selectedTrailer?.type.orEmpty(), + contentTitle = meta.name, + playbackSource = trailerPlaybackSource, + isLoading = trailerLoading, + errorMessage = trailerErrorMessage, + onDismiss = { + trailerRequestToken += 1 + trailerLoading = false + trailerPlaybackSource = null + trailerErrorMessage = null + selectedTrailer = null + }, + onRetry = selectedTrailer?.let { trailer -> + { resolveTrailer(trailer) } + }, + ) + } TraktListPickerDialog( visible = showLibraryListPicker, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt index b380fd17..a0f30bbc 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt @@ -1,5 +1,6 @@ package com.nuvio.app.features.player +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.features.player.skip.NextEpisodeThresholdMode import com.nuvio.app.features.streams.StreamAutoPlayMode import com.nuvio.app.features.streams.StreamAutoPlaySource @@ -141,6 +142,17 @@ object PlayerSettingsRepository { ?: StreamAutoPlaySource.ALL_SOURCES streamAutoPlaySelectedAddons = PlayerSettingsStorage.loadStreamAutoPlaySelectedAddons() ?: emptySet() streamAutoPlaySelectedPlugins = PlayerSettingsStorage.loadStreamAutoPlaySelectedPlugins() ?: emptySet() + if (!AppFeaturePolicy.pluginsEnabled) { + val normalizedSource = normalizeStreamAutoPlaySource(streamAutoPlaySource) + if (normalizedSource != streamAutoPlaySource) { + streamAutoPlaySource = normalizedSource + PlayerSettingsStorage.saveStreamAutoPlaySource(normalizedSource.name) + } + if (streamAutoPlaySelectedPlugins.isNotEmpty()) { + streamAutoPlaySelectedPlugins = emptySet() + PlayerSettingsStorage.saveStreamAutoPlaySelectedPlugins(emptySet()) + } + } streamAutoPlayRegex = PlayerSettingsStorage.loadStreamAutoPlayRegex() ?: "" streamAutoPlayTimeoutSeconds = PlayerSettingsStorage.loadStreamAutoPlayTimeoutSeconds() ?: 3 skipIntroEnabled = PlayerSettingsStorage.loadSkipIntroEnabled() ?: true @@ -261,10 +273,11 @@ object PlayerSettingsRepository { fun setStreamAutoPlaySource(source: StreamAutoPlaySource) { ensureLoaded() - if (streamAutoPlaySource == source) return - streamAutoPlaySource = source + val normalizedSource = normalizeStreamAutoPlaySource(source) + if (streamAutoPlaySource == normalizedSource) return + streamAutoPlaySource = normalizedSource publish() - PlayerSettingsStorage.saveStreamAutoPlaySource(source.name) + PlayerSettingsStorage.saveStreamAutoPlaySource(normalizedSource.name) } fun setStreamAutoPlaySelectedAddons(addons: Set) { @@ -277,10 +290,11 @@ object PlayerSettingsRepository { fun setStreamAutoPlaySelectedPlugins(plugins: Set) { ensureLoaded() - if (streamAutoPlaySelectedPlugins == plugins) return - streamAutoPlaySelectedPlugins = plugins + val normalizedPlugins = if (AppFeaturePolicy.pluginsEnabled) plugins else emptySet() + if (streamAutoPlaySelectedPlugins == normalizedPlugins) return + streamAutoPlaySelectedPlugins = normalizedPlugins publish() - PlayerSettingsStorage.saveStreamAutoPlaySelectedPlugins(plugins) + PlayerSettingsStorage.saveStreamAutoPlaySelectedPlugins(normalizedPlugins) } fun setStreamAutoPlayRegex(regex: String) { @@ -392,4 +406,12 @@ object PlayerSettingsRepository { nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd, ) } + + private fun normalizeStreamAutoPlaySource(source: StreamAutoPlaySource): StreamAutoPlaySource { + return if (!AppFeaturePolicy.pluginsEnabled && source == StreamAutoPlaySource.ENABLED_PLUGINS_ONLY) { + StreamAutoPlaySource.ALL_SOURCES + } else { + source + } + } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt index efaed602..f53b6274 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt @@ -1,6 +1,7 @@ package com.nuvio.app.features.player import co.touchlab.kermit.Logger +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.httpGetText import com.nuvio.app.features.details.MetaDetailsRepository @@ -149,8 +150,12 @@ object PlayerStreamsRepository { } val installedAddons = AddonRepository.uiState.value.addons - PluginRepository.initialize() - val pluginScrapers = PluginRepository.getEnabledScrapersForType(type) + val pluginScrapers = if (AppFeaturePolicy.pluginsEnabled) { + PluginRepository.initialize() + PluginRepository.getEnabledScrapersForType(type) + } else { + emptyList() + } if (installedAddons.isEmpty() && pluginScrapers.isEmpty()) { stateFlow.value = StreamsUiState( 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 5ee53318..654e147f 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 @@ -132,7 +132,9 @@ object ProfileRepository { LibraryRepository.onProfileChanged(profileIndex) WatchProgressRepository.onProfileChanged(profileIndex) AddonRepository.onProfileChanged(profileIndex) - PluginRepository.onProfileChanged(profileIndex) + if (com.nuvio.app.core.build.AppFeaturePolicy.pluginsEnabled) { + PluginRepository.onProfileChanged(profileIndex) + } ThemeSettingsRepository.onProfileChanged() PlayerSettingsRepository.onProfileChanged() HomeCatalogSettingsRepository.onProfileChanged() 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 57d083ff..202436ed 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 @@ -8,6 +8,7 @@ import androidx.compose.material.icons.rounded.Tune internal fun LazyListScope.contentDiscoveryContent( isTablet: Boolean, + showPluginsEntry: Boolean, onAddonsClick: () -> Unit, onPluginsClick: () -> Unit, onHomescreenClick: () -> Unit, @@ -26,13 +27,15 @@ internal fun LazyListScope.contentDiscoveryContent( isTablet = isTablet, onClick = onAddonsClick, ) - SettingsNavigationRow( - title = "Plugins", - description = "Install JavaScript scraper repositories and test providers internally.", - icon = Icons.Rounded.Hub, - isTablet = isTablet, - onClick = onPluginsClick, - ) + if (showPluginsEntry) { + SettingsNavigationRow( + title = "Plugins", + description = "Install JavaScript scraper repositories and test providers internally.", + icon = Icons.Rounded.Hub, + isTablet = isTablet, + onClick = onPluginsClick, + ) + } } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt index 26301ebf..64589c41 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt @@ -1,5 +1,6 @@ package com.nuvio.app.features.settings +import com.nuvio.app.core.build.AppFeaturePolicy import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -51,6 +52,7 @@ import com.nuvio.app.features.player.AvailableLanguageOptions import com.nuvio.app.features.player.PlayerSettingsRepository import com.nuvio.app.features.player.SubtitleLanguageOption import com.nuvio.app.features.player.languageLabelForCode +import com.nuvio.app.features.plugins.PluginsUiState import com.nuvio.app.features.plugins.PluginRepository import com.nuvio.app.features.streams.StreamAutoPlayMode import com.nuvio.app.features.streams.StreamAutoPlaySource @@ -111,9 +113,15 @@ private fun PlaybackSettingsSection( var showAutoPlayAddonSelectionDialog by remember { mutableStateOf(false) } var showAutoPlayPluginSelectionDialog by remember { mutableStateOf(false) } var showAutoPlayRegexDialog by remember { mutableStateOf(false) } + val pluginsEnabled = AppFeaturePolicy.pluginsEnabled val autoPlayPlayerSettings by PlayerSettingsRepository.uiState.collectAsStateWithLifecycle() val addonUiState by AddonRepository.uiState.collectAsStateWithLifecycle() - val pluginUiState by PluginRepository.uiState.collectAsStateWithLifecycle() + val pluginUiState = if (pluginsEnabled) { + val state by PluginRepository.uiState.collectAsStateWithLifecycle() + state + } else { + PluginsUiState(pluginsEnabled = false) + } val hapticFeedback = LocalHapticFeedback.current val sectionSpacing = if (isTablet) 18.dp else 12.dp @@ -292,7 +300,7 @@ private fun PlaybackSettingsSection( SettingsNavigationRow( title = "Source Scope", description = when (autoPlayPlayerSettings.streamAutoPlaySource) { - StreamAutoPlaySource.ALL_SOURCES -> "All Sources" + StreamAutoPlaySource.ALL_SOURCES -> if (pluginsEnabled) "All Sources" else "All Addons" StreamAutoPlaySource.INSTALLED_ADDONS_ONLY -> "Installed Addons Only" StreamAutoPlaySource.ENABLED_PLUGINS_ONLY -> "Enabled Plugins Only" }, @@ -313,7 +321,7 @@ private fun PlaybackSettingsSection( onClick = { showAutoPlayAddonSelectionDialog = true }, ) } - if (autoPlayPlayerSettings.streamAutoPlaySource != StreamAutoPlaySource.INSTALLED_ADDONS_ONLY) { + if (pluginsEnabled && autoPlayPlayerSettings.streamAutoPlaySource != StreamAutoPlaySource.INSTALLED_ADDONS_ONLY) { SettingsGroupDivider(isTablet = isTablet) val pluginSubtitle = if (autoPlayPlayerSettings.streamAutoPlaySelectedPlugins.isEmpty()) { "All Plugins" @@ -678,6 +686,7 @@ private fun PlaybackSettingsSection( if (showAutoPlaySourceDialog) { StreamAutoPlaySourceDialog( + pluginsEnabled = pluginsEnabled, selectedSource = autoPlayPlayerSettings.streamAutoPlaySource, onSourceSelected = { PlayerSettingsRepository.setStreamAutoPlaySource(it) @@ -707,7 +716,7 @@ private fun PlaybackSettingsSection( ) } - if (showAutoPlayPluginSelectionDialog) { + if (pluginsEnabled && showAutoPlayPluginSelectionDialog) { val pluginNames = pluginUiState.scrapers .filter { it.enabled } .map { it.name } @@ -1121,15 +1130,40 @@ private fun StreamAutoPlayModeDialog( @Composable @OptIn(ExperimentalMaterial3Api::class) private fun StreamAutoPlaySourceDialog( + pluginsEnabled: Boolean, selectedSource: StreamAutoPlaySource, onSourceSelected: (StreamAutoPlaySource) -> Unit, onDismiss: () -> Unit, ) { - val options = listOf( - Triple(StreamAutoPlaySource.ALL_SOURCES, "All Sources", "Consider streams from both addons and plugins."), - Triple(StreamAutoPlaySource.INSTALLED_ADDONS_ONLY, "Installed Addons Only", "Only consider streams from installed addons."), - Triple(StreamAutoPlaySource.ENABLED_PLUGINS_ONLY, "Enabled Plugins Only", "Only consider streams from enabled plugins."), - ) + val options = buildList { + add( + Triple( + StreamAutoPlaySource.ALL_SOURCES, + if (pluginsEnabled) "All Sources" else "All Addons", + if (pluginsEnabled) { + "Consider streams from both addons and plugins." + } else { + "Consider streams from all installed addons." + }, + ), + ) + add( + Triple( + StreamAutoPlaySource.INSTALLED_ADDONS_ONLY, + "Installed Addons Only", + "Only consider streams from installed addons.", + ), + ) + if (pluginsEnabled) { + add( + Triple( + StreamAutoPlaySource.ENABLED_PLUGINS_ONLY, + "Enabled Plugins Only", + "Only consider streams from enabled plugins.", + ), + ) + } + } BasicAlertDialog( onDismissRequest = onDismiss, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt index 45a87f89..17121d0f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt @@ -1,5 +1,6 @@ package com.nuvio.app.features.settings +import com.nuvio.app.core.build.AppFeaturePolicy import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -135,6 +136,11 @@ fun AddonsSettingsScreen( fun PluginsSettingsScreen( onBack: () -> Unit, ) { + if (!AppFeaturePolicy.pluginsEnabled) { + AddonsSettingsScreen(onBack = onBack) + return + } + LaunchedEffect(Unit) { PluginRepository.initialize() } 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 a17d932d..df6488c9 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 @@ -1,5 +1,7 @@ package com.nuvio.app.features.settings +import com.nuvio.app.core.build.AppFeaturePolicy + import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -290,13 +292,14 @@ private fun MobileSettingsScreen( ) SettingsPage.ContentDiscovery -> contentDiscoveryContent( isTablet = false, + showPluginsEntry = AppFeaturePolicy.pluginsEnabled, onAddonsClick = onAddonsClick, onPluginsClick = onPluginsClick, onHomescreenClick = onHomescreenClick, onMetaScreenClick = onMetaScreenClick, ) SettingsPage.Addons -> addonsSettingsContent() - SettingsPage.Plugins -> pluginsSettingsContent() + SettingsPage.Plugins -> if (AppFeaturePolicy.pluginsEnabled) pluginsSettingsContent() else addonsSettingsContent() SettingsPage.Homescreen -> homescreenSettingsContent( isTablet = false, heroEnabled = homescreenHeroEnabled, @@ -484,13 +487,14 @@ private fun TabletSettingsScreen( ) SettingsPage.ContentDiscovery -> contentDiscoveryContent( isTablet = true, + showPluginsEntry = AppFeaturePolicy.pluginsEnabled, onAddonsClick = { openInlinePage(SettingsPage.Addons) }, onPluginsClick = { openInlinePage(SettingsPage.Plugins) }, onHomescreenClick = { openInlinePage(SettingsPage.Homescreen) }, onMetaScreenClick = { openInlinePage(SettingsPage.MetaScreen) }, ) SettingsPage.Addons -> addonsSettingsContent() - SettingsPage.Plugins -> pluginsSettingsContent() + SettingsPage.Plugins -> if (AppFeaturePolicy.pluginsEnabled) pluginsSettingsContent() else addonsSettingsContent() SettingsPage.Homescreen -> homescreenSettingsContent( isTablet = true, heroEnabled = homescreenHeroEnabled, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt index d40d13ac..e06edfa2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt @@ -1,11 +1,13 @@ package com.nuvio.app.features.streams import co.touchlab.kermit.Logger +import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.httpGetText import com.nuvio.app.features.details.MetaDetailsRepository import com.nuvio.app.features.player.PlayerSettingsRepository import com.nuvio.app.features.plugins.PluginRepository +import com.nuvio.app.features.plugins.PluginsUiState import com.nuvio.app.features.plugins.PluginRepositoryItem import com.nuvio.app.features.plugins.PluginRuntimeResult import com.nuvio.app.features.plugins.PluginScraper @@ -39,8 +41,12 @@ object StreamsRepository { } private fun load(type: String, videoId: String, season: Int?, episode: Int?, forceRefresh: Boolean) { - PluginRepository.initialize() - val pluginUiState = PluginRepository.uiState.value + val pluginUiState = if (AppFeaturePolicy.pluginsEnabled) { + PluginRepository.initialize() + PluginRepository.uiState.value + } else { + PluginsUiState(pluginsEnabled = false) + } val requestKey = "$type::$videoId::$season::$episode::pluginsGrouped=${pluginUiState.groupStreamsByRepository}" val currentState = _uiState.value if ( @@ -89,7 +95,11 @@ object StreamsRepository { } val installedAddons = AddonRepository.uiState.value.addons - val pluginScrapers = PluginRepository.getEnabledScrapersForType(type) + val pluginScrapers = if (AppFeaturePolicy.pluginsEnabled) { + PluginRepository.getEnabledScrapersForType(type) + } else { + emptyList() + } val pluginProviderGroups = pluginScrapers.toPluginProviderGroups( repositories = pluginUiState.repositories, groupByRepository = pluginUiState.groupStreamsByRepository,