From 4b45d4dd253f5641540a2484adcf293f31e2abb6 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Fri, 22 May 2026 20:29:51 +0530 Subject: [PATCH] ref: adding attributes --- .../composeResources/values/strings.xml | 4 ++ .../components/HomeContinueWatchingSection.kt | 28 ++++++++- .../features/settings/DebridSettingsPage.kt | 44 +++++++++---- .../settings/LicensesAttributionsPage.kt | 63 ++++++++++++++++--- .../app/features/settings/SettingsSearch.kt | 2 + 5 files changed, 121 insertions(+), 20 deletions(-) diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index b6917cd2..92ee5269 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -425,6 +425,10 @@ Nuvio uses IMDb Non-Commercial Datasets, including title.ratings.tsv.gz, for IMDb ratings and vote counts. Information courtesy of IMDb (https://www.imdb.com). Used with permission. IMDb data is for personal and non-commercial use under IMDb's terms. Trakt Nuvio connects to Trakt for account authentication, watched history, progress sync, library data, ratings, lists, and comments. Nuvio is not affiliated with or endorsed by Trakt. + Premiumize + Nuvio connects to Premiumize for account authentication, cloud library access, cache checks, and cloud playback features. Nuvio is not affiliated with or endorsed by Premiumize. + TorBox + Nuvio connects to TorBox for account authentication, cloud library access, cache checks, and cloud playback features. Nuvio is not affiliated with or endorsed by TorBox. MDBList Nuvio uses MDBList for ratings and external score provider data. Nuvio is not affiliated with or endorsed by MDBList. IntroDB diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt index 804a5e22..f70408b1 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt @@ -104,6 +104,27 @@ private fun ContinueWatchingItem.continueWatchingArtworkUrl( ) } +private fun ContinueWatchingItem.continueWatchingPosterArtworkUrl( + useEpisodeThumbnails: Boolean, +): String? { + if (seasonNumber == null || episodeNumber == null) { + return continueWatchingArtworkUrl(useEpisodeThumbnails) + } + + val normalizedEpisodeThumbnail = episodeThumbnail?.trim()?.takeIf { it.isNotBlank() } + val nonEpisodeImageUrl = imageUrl + ?.trim() + ?.takeIf { it.isNotBlank() && it != normalizedEpisodeThumbnail } + + return firstNonBlank( + poster, + background, + nonEpisodeImageUrl, + if (useEpisodeThumbnails) episodeThumbnail else null, + imageUrl, + ) +} + private fun firstNonBlank(vararg values: String?): String? = values.firstOrNull { value -> !value.isNullOrBlank() }?.trim() @@ -508,8 +529,11 @@ private fun ContinueWatchingPosterCard( ) .posterCardClickable(onClick = onClick, onLongClick = onLongClick), ) { - val shouldBlurArtwork = blurNextUp && useEpisodeThumbnails && item.isNextUp - val imageUrl = item.continueWatchingArtworkUrl(useEpisodeThumbnails) + val imageUrl = item.continueWatchingPosterArtworkUrl(useEpisodeThumbnails) + val shouldBlurArtwork = blurNextUp && + useEpisodeThumbnails && + item.isNextUp && + imageUrl == firstNonBlank(item.episodeThumbnail) if (imageUrl != null) { AsyncImage( model = cloudLibraryDisplayArtworkUrl(imageUrl), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt index 3ea86ce7..80eb22b1 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt @@ -131,7 +131,6 @@ internal fun LazyListScope.debridSettingsContent( ) { item { var showResolverProviderDialog by rememberSaveable { mutableStateOf(false) } - val uriHandler = LocalUriHandler.current val resolverProviders = settings.resolverServices.map { it.provider } val activeResolverProvider = settings.activeResolverCredential?.provider SettingsSection( @@ -144,15 +143,6 @@ internal fun LazyListScope.debridSettingsContent( text = stringResource(Res.string.settings_debrid_experimental_notice), ) SettingsGroupDivider(isTablet = isTablet) - DebridPreferenceRow( - isTablet = isTablet, - title = "Learn more", - description = "Cloud Library, connected accounts, and playable-link preparation.", - value = "Open", - enabled = true, - onClick = { runCatching { uriHandler.openUri(CLOUD_SERVICES_FAQ_URL) } }, - ) - SettingsGroupDivider(isTablet = isTablet) SettingsSwitchRow( title = stringResource(Res.string.settings_debrid_cloud_library), description = stringResource(Res.string.settings_debrid_cloud_library_description), @@ -272,7 +262,10 @@ internal fun LazyListScope.debridSettingsContent( } } - if (!settings.canResolvePlayableLinks) return + if (!settings.canResolvePlayableLinks) { + debridLearnMoreFooterItem(isTablet) + return + } item { var showPrepareCountDialog by rememberSaveable { mutableStateOf(false) } @@ -461,6 +454,35 @@ internal fun LazyListScope.debridSettingsContent( null -> Unit } } + + debridLearnMoreFooterItem(isTablet) +} + +private fun LazyListScope.debridLearnMoreFooterItem(isTablet: Boolean) { + item { + val uriHandler = LocalUriHandler.current + DebridLearnMoreFooter( + isTablet = isTablet, + onClick = { runCatching { uriHandler.openUri(CLOUD_SERVICES_FAQ_URL) } }, + ) + } +} + +@Composable +private fun DebridLearnMoreFooter( + isTablet: Boolean, + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = if (isTablet) 4.dp else 0.dp, bottom = if (isTablet) 10.dp else 6.dp), + contentAlignment = Alignment.Center, + ) { + TextButton(onClick = onClick) { + Text("Learn more") + } + } } private enum class DebridTemplateField { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt index 7efecdf5..d86eb0a6 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt @@ -28,8 +28,12 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage import com.nuvio.app.core.ui.NuvioScreen import com.nuvio.app.core.ui.NuvioScreenHeader +import com.nuvio.app.features.cloud.PremiumizeCloudLibraryPosterUrl +import com.nuvio.app.features.cloud.TorboxCloudLibraryPosterUrl +import com.nuvio.app.features.cloud.cloudLibraryDisplayArtworkUrl import com.nuvio.app.isIos import nuvio.composeapp.generated.resources.* import org.jetbrains.compose.resources.StringResource @@ -38,6 +42,8 @@ import org.jetbrains.compose.resources.stringResource private const val TmdbUrl = "https://www.themoviedb.org" private const val ImdbDatasetsUrl = "https://developer.imdb.com/non-commercial-datasets/" private const val TraktUrl = "https://trakt.tv" +private const val PremiumizeUrl = "https://www.premiumize.me" +private const val TorboxUrl = "https://torbox.app" private const val MdbListUrl = "https://mdblist.com" private const val IntroDbUrl = "https://introdb.app/" private const val NuvioRepositoryUrl = "https://github.com/NuvioMedia/NuvioMobile" @@ -48,6 +54,7 @@ private data class AttributionItem( val titleRes: StringResource, val bodyRes: StringResource, val logo: IntegrationLogo?, + val logoUrl: String? = null, val link: String, ) @@ -165,14 +172,26 @@ private fun AttributionRow( body = stringResource(item.bodyRes), link = item.link, isTablet = isTablet, - leading = item.logo?.let { logo -> - { - IntegrationLogoImage( - painter = integrationLogoPainter(logo), - contentDescription = title, - isTablet = isTablet, - ) + leading = when { + item.logo != null -> item.logo.let { logo -> + { + IntegrationLogoImage( + painter = integrationLogoPainter(logo), + contentDescription = title, + isTablet = isTablet, + ) + } } + item.logoUrl != null -> item.logoUrl.let { logoUrl -> + { + ProviderLogoImage( + url = logoUrl, + contentDescription = title, + isTablet = isTablet, + ) + } + } + else -> null }, onOpen = { uriHandler.openUri(item.link) }, ) @@ -274,6 +293,22 @@ private fun IntegrationLogoImage( ) } +@Composable +private fun ProviderLogoImage( + url: String, + contentDescription: String, + isTablet: Boolean, +) { + AsyncImage( + model = url, + contentDescription = contentDescription, + modifier = Modifier + .padding(top = 2.dp) + .size(if (isTablet) 46.dp else 40.dp), + contentScale = ContentScale.Fit, + ) +} + @Composable private fun PlainStackDivider() { HorizontalDivider( @@ -295,6 +330,20 @@ private fun attributionItems(): List = listOf( logo = IntegrationLogo.Trakt, link = TraktUrl, ), + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_premiumize_title, + bodyRes = Res.string.settings_licenses_attributions_premiumize_body, + logo = null, + logoUrl = PremiumizeCloudLibraryPosterUrl, + link = PremiumizeUrl, + ), + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_torbox_title, + bodyRes = Res.string.settings_licenses_attributions_torbox_body, + logo = null, + logoUrl = cloudLibraryDisplayArtworkUrl(TorboxCloudLibraryPosterUrl), + link = TorboxUrl, + ), AttributionItem( titleRes = Res.string.settings_licenses_attributions_mdblist_title, bodyRes = Res.string.settings_licenses_attributions_mdblist_body, 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 978ea2e2..03187cba 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 @@ -262,6 +262,8 @@ internal fun settingsSearchEntries( PlaybackSearchRow("nuvio-license", stringResource(Res.string.settings_licenses_attributions_nuvio_title), stringResource(Res.string.settings_licenses_attributions_nuvio_license)), PlaybackSearchRow("tmdb-attribution", stringResource(Res.string.settings_licenses_attributions_tmdb_title), stringResource(Res.string.settings_licenses_attributions_tmdb_body)), PlaybackSearchRow("trakt-attribution", stringResource(Res.string.settings_licenses_attributions_trakt_title), stringResource(Res.string.settings_licenses_attributions_trakt_body)), + PlaybackSearchRow("premiumize-attribution", stringResource(Res.string.settings_licenses_attributions_premiumize_title), stringResource(Res.string.settings_licenses_attributions_premiumize_body)), + PlaybackSearchRow("torbox-attribution", stringResource(Res.string.settings_licenses_attributions_torbox_title), stringResource(Res.string.settings_licenses_attributions_torbox_body)), PlaybackSearchRow("mdblist-attribution", stringResource(Res.string.settings_licenses_attributions_mdblist_title), stringResource(Res.string.settings_licenses_attributions_mdblist_body)), PlaybackSearchRow("introdb-attribution", stringResource(Res.string.settings_licenses_attributions_introdb_title), stringResource(Res.string.settings_licenses_attributions_introdb_body)), PlaybackSearchRow("imdb-datasets", stringResource(Res.string.settings_licenses_attributions_imdb_title), stringResource(Res.string.settings_licenses_attributions_imdb_body)),