diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt index 23f99ee8..a2140bde 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import com.nuvio.app.R import nuvio.composeapp.generated.resources.Res +import nuvio.composeapp.generated.resources.introdb_favicon import nuvio.composeapp.generated.resources.rating_tmdb import org.jetbrains.compose.resources.painterResource as composePainterResource @@ -14,4 +15,5 @@ internal actual fun integrationLogoPainter(logo: IntegrationLogo): Painter = IntegrationLogo.Tmdb -> composePainterResource(Res.drawable.rating_tmdb) IntegrationLogo.Trakt -> painterResource(id = R.drawable.trakt_tv_favicon) IntegrationLogo.MdbList -> painterResource(id = R.drawable.mdblist_logo) + IntegrationLogo.IntroDb -> composePainterResource(Res.drawable.introdb_favicon) } diff --git a/composeApp/src/commonMain/composeResources/drawable/introdb_favicon.png b/composeApp/src/commonMain/composeResources/drawable/introdb_favicon.png new file mode 100644 index 00000000..e3e357b9 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/introdb_favicon.png differ diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 55d0bbf0..86c0d019 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1,4 +1,5 @@ + Data sources, acknowledgements, and platform licenses Open recognition and project credits Back Cancel @@ -366,6 +367,7 @@ Continue Watching Home Layout Integrations + Licenses & Attribution MDBList Ratings Detail Page Notifications @@ -394,6 +396,28 @@ No settings found. Search settings... RESULTS + APP LICENSE + DATA & SERVICES + PLAYBACK LICENSE + Nuvio Mobile + Source code and license terms are available in the project repository. + Licensed under the GNU General Public License v3.0. + The Movie Database (TMDB) + Nuvio uses the TMDB API for movie and TV metadata, artwork, trailers, cast, production details, collections, and recommendations. This product uses the TMDB API but is not endorsed or certified by TMDB. + IMDb Non-Commercial Datasets + 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. + MDBList + Nuvio uses MDBList for ratings and external score provider data. Nuvio is not affiliated with or endorsed by MDBList. + IntroDB + Nuvio uses the IntroDB API for community-provided intro, recap, credits, and preview timestamps used by skip controls. Nuvio is not affiliated with or endorsed by IntroDB. + MPVKit + Used for playback on iOS builds. + MPVKit source alone is licensed under LGPL v3.0. MPVKit bundles, including libmpv and FFmpeg libraries, are also licensed under LGPL v3.0. + AndroidX Media3 ExoPlayer 1.8.0 + Used for playback on Android builds. + Licensed under the Apache License, Version 2.0. Loading your Trakt lists… Choose where to save this title on Trakt Donate diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 1d605c3c..9a7be453 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -145,6 +145,7 @@ import com.nuvio.app.features.settings.AddonsSettingsScreen import com.nuvio.app.features.settings.PluginsSettingsScreen import com.nuvio.app.features.settings.AccountSettingsScreen import com.nuvio.app.features.settings.SupportersContributorsSettingsScreen +import com.nuvio.app.features.settings.LicensesAttributionsSettingsScreen import com.nuvio.app.features.settings.ThemeSettingsRepository import com.nuvio.app.features.collection.CollectionManagementScreen import com.nuvio.app.features.collection.CollectionEditorScreen @@ -237,6 +238,9 @@ object AccountSettingsRoute @Serializable object SupportersContributorsSettingsRoute +@Serializable +object LicensesAttributionsSettingsRoute + @Serializable object CollectionsRoute @@ -1065,6 +1069,9 @@ private fun MainAppContent( onSupportersContributorsSettingsClick = { navController.navigate(SupportersContributorsSettingsRoute) }, + onLicensesAttributionsSettingsClick = { + navController.navigate(LicensesAttributionsSettingsRoute) + }, onCheckForUpdatesClick = if (AppFeaturePolicy.inAppUpdaterEnabled) { { appUpdaterController.checkForUpdates( @@ -1698,6 +1705,15 @@ private fun MainAppContent( onBack = onBack, ) } + composable { backStackEntry -> + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + ) + LicensesAttributionsSettingsScreen( + onBack = onBack, + ) + } composable { backStackEntry -> val onBack = rememberGuardedPopBackStack( navController = navController, @@ -1972,6 +1988,7 @@ private fun AppTabHost( onPluginsSettingsClick: () -> Unit = {}, onAccountSettingsClick: () -> Unit = {}, onSupportersContributorsSettingsClick: () -> Unit = {}, + onLicensesAttributionsSettingsClick: () -> Unit = {}, onCheckForUpdatesClick: (() -> Unit)? = null, onCollectionsSettingsClick: () -> Unit = {}, onFolderClick: ((collectionId: String, folderId: String) -> Unit)? = null, @@ -2024,6 +2041,7 @@ private fun AppTabHost( onPluginsClick = onPluginsSettingsClick, onAccountClick = onAccountSettingsClick, onSupportersContributorsClick = onSupportersContributorsSettingsClick, + onLicensesAttributionsClick = onLicensesAttributionsSettingsClick, onCheckForUpdatesClick = onCheckForUpdatesClick, onCollectionsClick = onCollectionsSettingsClick, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt index 4871bb16..8bf7971d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt @@ -7,6 +7,7 @@ internal enum class IntegrationLogo { Tmdb, Trakt, MdbList, + IntroDb, } @Composable 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 new file mode 100644 index 00000000..7efecdf5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt @@ -0,0 +1,341 @@ +package com.nuvio.app.features.settings + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.OpenInNew +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +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 com.nuvio.app.core.ui.NuvioScreen +import com.nuvio.app.core.ui.NuvioScreenHeader +import com.nuvio.app.isIos +import nuvio.composeapp.generated.resources.* +import org.jetbrains.compose.resources.StringResource +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 MdbListUrl = "https://mdblist.com" +private const val IntroDbUrl = "https://introdb.app/" +private const val NuvioRepositoryUrl = "https://github.com/NuvioMedia/NuvioMobile" +private const val MpvKitUrl = "https://github.com/mpvkit/MPVKit" +private const val ApacheLicenseUrl = "https://www.apache.org/licenses/LICENSE-2.0" + +private data class AttributionItem( + val titleRes: StringResource, + val bodyRes: StringResource, + val logo: IntegrationLogo?, + val link: String, +) + +private data class LicenseItem( + val titleRes: StringResource, + val bodyRes: StringResource, + val licenseRes: StringResource, + val link: String, +) + +@Composable +fun LicensesAttributionsSettingsScreen( + onBack: () -> Unit, +) { + NuvioScreen( + modifier = Modifier.fillMaxSize(), + ) { + stickyHeader { + NuvioScreenHeader( + title = stringResource(Res.string.compose_settings_page_licenses_attributions), + onBack = onBack, + ) + } + licensesAttributionsContent(isTablet = false) + } +} + +internal fun LazyListScope.licensesAttributionsContent( + isTablet: Boolean, +) { + item { + LicensesAttributionsBody(isTablet = isTablet) + } +} + +@Composable +private fun LicensesAttributionsBody( + isTablet: Boolean, +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(if (isTablet) 28.dp else 24.dp), + ) { + PlainSettingsStack( + title = stringResource(Res.string.settings_licenses_attributions_section_app), + isTablet = isTablet, + ) { + LicenseRow( + item = appLicenseItem(), + isTablet = isTablet, + ) + } + + PlainSettingsStack( + title = stringResource(Res.string.settings_licenses_attributions_section_data), + isTablet = isTablet, + ) { + val items = attributionItems() + items.forEachIndexed { index, item -> + AttributionRow( + item = item, + isTablet = isTablet, + ) + if (index != items.lastIndex) { + PlainStackDivider() + } + } + } + + PlainSettingsStack( + title = stringResource(Res.string.settings_licenses_attributions_section_playback), + isTablet = isTablet, + ) { + LicenseRow( + item = platformLicenseItem(), + isTablet = isTablet, + ) + } + } +} + +@Composable +private fun PlainSettingsStack( + title: String, + isTablet: Boolean, + content: @Composable () -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = title, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontWeight = FontWeight.Bold, + ) + Spacer(modifier = Modifier.height(if (isTablet) 12.dp else 10.dp)) + Column( + modifier = Modifier.fillMaxWidth(), + ) { + content() + } + } +} + +@Composable +private fun AttributionRow( + item: AttributionItem, + isTablet: Boolean, +) { + val uriHandler = LocalUriHandler.current + val title = stringResource(item.titleRes) + LinkedPlainRow( + title = title, + body = stringResource(item.bodyRes), + link = item.link, + isTablet = isTablet, + leading = item.logo?.let { logo -> + { + IntegrationLogoImage( + painter = integrationLogoPainter(logo), + contentDescription = title, + isTablet = isTablet, + ) + } + }, + onOpen = { uriHandler.openUri(item.link) }, + ) +} + +@Composable +private fun LicenseRow( + item: LicenseItem, + isTablet: Boolean, +) { + val uriHandler = LocalUriHandler.current + val itemBody = stringResource(item.bodyRes) + val itemLicense = stringResource(item.licenseRes) + val body = buildString { + append(itemBody) + append("\n") + append(itemLicense) + } + LinkedPlainRow( + title = stringResource(item.titleRes), + body = body, + link = item.link, + isTablet = isTablet, + onOpen = { uriHandler.openUri(item.link) }, + ) +} + +@Composable +private fun LinkedPlainRow( + title: String, + body: String, + link: String, + isTablet: Boolean, + leading: (@Composable () -> Unit)? = null, + onOpen: () -> Unit, +) { + val verticalPadding = if (isTablet) 18.dp else 16.dp + val horizontalPadding = if (isTablet) 4.dp else 0.dp + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onOpen) + .padding(horizontal = horizontalPadding, vertical = verticalPadding), + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.spacedBy(if (isTablet) 18.dp else 14.dp), + ) { + leading?.invoke() + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = body, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Text( + text = link, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Icon( + imageVector = Icons.AutoMirrored.Rounded.OpenInNew, + contentDescription = null, + modifier = Modifier + .padding(top = 2.dp) + .size(if (isTablet) 22.dp else 20.dp) + .alpha(0.72f), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } +} + +@Composable +private fun IntegrationLogoImage( + painter: Painter, + contentDescription: String, + isTablet: Boolean, +) { + Image( + painter = painter, + contentDescription = contentDescription, + modifier = Modifier + .padding(top = 2.dp) + .size(if (isTablet) 46.dp else 40.dp), + contentScale = ContentScale.Fit, + ) +} + +@Composable +private fun PlainStackDivider() { + HorizontalDivider( + thickness = 0.5.dp, + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.18f), + ) +} + +private fun attributionItems(): List = listOf( + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_tmdb_title, + bodyRes = Res.string.settings_licenses_attributions_tmdb_body, + logo = IntegrationLogo.Tmdb, + link = TmdbUrl, + ), + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_trakt_title, + bodyRes = Res.string.settings_licenses_attributions_trakt_body, + logo = IntegrationLogo.Trakt, + link = TraktUrl, + ), + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_mdblist_title, + bodyRes = Res.string.settings_licenses_attributions_mdblist_body, + logo = IntegrationLogo.MdbList, + link = MdbListUrl, + ), + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_introdb_title, + bodyRes = Res.string.settings_licenses_attributions_introdb_body, + logo = IntegrationLogo.IntroDb, + link = IntroDbUrl, + ), + AttributionItem( + titleRes = Res.string.settings_licenses_attributions_imdb_title, + bodyRes = Res.string.settings_licenses_attributions_imdb_body, + logo = null, + link = ImdbDatasetsUrl, + ), +) + +private fun appLicenseItem(): LicenseItem = + LicenseItem( + titleRes = Res.string.settings_licenses_attributions_nuvio_title, + bodyRes = Res.string.settings_licenses_attributions_nuvio_body, + licenseRes = Res.string.settings_licenses_attributions_nuvio_license, + link = NuvioRepositoryUrl, + ) + +private fun platformLicenseItem(): LicenseItem = + if (isIos) { + LicenseItem( + titleRes = Res.string.settings_licenses_attributions_mpvkit_title, + bodyRes = Res.string.settings_licenses_attributions_mpvkit_body, + licenseRes = Res.string.settings_licenses_attributions_mpvkit_license, + link = MpvKitUrl, + ) + } else { + LicenseItem( + titleRes = Res.string.settings_licenses_attributions_exoplayer_title, + bodyRes = Res.string.settings_licenses_attributions_exoplayer_body, + licenseRes = Res.string.settings_licenses_attributions_exoplayer_license, + link = ApacheLicenseUrl, + ) + } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt index e66779fd..d030a785 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsModels.kt @@ -16,6 +16,7 @@ import nuvio.composeapp.generated.resources.compose_settings_page_content_discov import nuvio.composeapp.generated.resources.compose_settings_page_continue_watching import nuvio.composeapp.generated.resources.compose_settings_page_homescreen import nuvio.composeapp.generated.resources.compose_settings_page_integrations +import nuvio.composeapp.generated.resources.compose_settings_page_licenses_attributions import nuvio.composeapp.generated.resources.compose_settings_page_mdblist_ratings import nuvio.composeapp.generated.resources.compose_settings_page_meta_screen import nuvio.composeapp.generated.resources.compose_settings_page_notifications @@ -58,6 +59,11 @@ internal enum class SettingsPage( category = SettingsCategory.About, parentPage = Root, ), + LicensesAttributions( + titleRes = Res.string.compose_settings_page_licenses_attributions, + category = SettingsCategory.About, + parentPage = Root, + ), Playback( titleRes = Res.string.compose_settings_page_playback, category = SettingsCategory.General, 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 e97576f6..71580c35 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 @@ -26,6 +26,7 @@ import nuvio.composeapp.generated.resources.compose_about_version_format import nuvio.composeapp.generated.resources.compose_settings_page_account import nuvio.composeapp.generated.resources.compose_settings_page_appearance import nuvio.composeapp.generated.resources.compose_settings_page_integrations +import nuvio.composeapp.generated.resources.compose_settings_page_licenses_attributions import nuvio.composeapp.generated.resources.compose_settings_page_notifications import nuvio.composeapp.generated.resources.compose_settings_page_playback import nuvio.composeapp.generated.resources.compose_settings_page_supporters_contributors @@ -48,6 +49,7 @@ import nuvio.composeapp.generated.resources.compose_settings_page_content_discov import nuvio.composeapp.generated.resources.compose_settings_page_trakt import nuvio.composeapp.generated.resources.settings_playback_subtitle import nuvio.composeapp.generated.resources.about_supporters_contributors_subtitle +import nuvio.composeapp.generated.resources.about_licenses_attributions_subtitle import org.jetbrains.compose.resources.stringResource internal fun LazyListScope.settingsRootContent( @@ -59,6 +61,7 @@ internal fun LazyListScope.settingsRootContent( onIntegrationsClick: () -> Unit, onTraktClick: () -> Unit, onSupportersContributorsClick: () -> Unit, + onLicensesAttributionsClick: () -> Unit, onCheckForUpdatesClick: (() -> Unit)? = null, onDownloadsClick: () -> Unit, onAccountClick: () -> Unit, @@ -175,6 +178,14 @@ internal fun LazyListScope.settingsRootContent( 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), + icon = Icons.Rounded.Info, + isTablet = isTablet, + onClick = onLicensesAttributionsClick, + ) if (onCheckForUpdatesClick != null) { SettingsGroupDivider(isTablet = isTablet) SettingsNavigationRow( 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 3cb0a92a..7d3b0e04 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 @@ -97,6 +97,7 @@ fun SettingsScreen( onDownloadsClick: () -> Unit = {}, onAccountClick: () -> Unit = {}, onSupportersContributorsClick: () -> Unit = {}, + onLicensesAttributionsClick: () -> Unit = {}, onCheckForUpdatesClick: (() -> Unit)? = null, onCollectionsClick: () -> Unit = {}, ) { @@ -243,6 +244,7 @@ fun SettingsScreen( onSwitchProfile = onSwitchProfile, onDownloadsClick = onDownloadsClick, onSupportersContributorsClick = onSupportersContributorsClick, + onLicensesAttributionsClick = onLicensesAttributionsClick, onCheckForUpdatesClick = onCheckForUpdatesClick, onCollectionsClick = onCollectionsClick, ) @@ -294,6 +296,7 @@ fun SettingsScreen( onDownloadsClick = onDownloadsClick, onAccountClick = onAccountClick, onSupportersContributorsClick = onSupportersContributorsClick, + onLicensesAttributionsClick = onLicensesAttributionsClick, onCheckForUpdatesClick = onCheckForUpdatesClick, onCollectionsClick = onCollectionsClick, ) @@ -349,6 +352,7 @@ private fun MobileSettingsScreen( onDownloadsClick: () -> Unit = {}, onAccountClick: () -> Unit = {}, onSupportersContributorsClick: () -> Unit = {}, + onLicensesAttributionsClick: () -> Unit = {}, onCheckForUpdatesClick: (() -> Unit)? = null, onCollectionsClick: () -> Unit = {}, ) { @@ -385,6 +389,7 @@ private fun MobileSettingsScreen( is SettingsSearchTarget.Page -> when (target.page) { SettingsPage.Account -> onAccountClick() SettingsPage.SupportersContributors -> onSupportersContributorsClick() + SettingsPage.LicensesAttributions -> onLicensesAttributionsClick() SettingsPage.ContinueWatching -> onContinueWatchingClick() SettingsPage.Addons -> onAddonsClick() SettingsPage.Plugins -> { @@ -443,6 +448,7 @@ private fun MobileSettingsScreen( onIntegrationsClick = { onPageChange(SettingsPage.Integrations) }, onTraktClick = { onPageChange(SettingsPage.TraktAuthentication) }, onSupportersContributorsClick = onSupportersContributorsClick, + onLicensesAttributionsClick = onLicensesAttributionsClick, onCheckForUpdatesClick = onCheckForUpdatesClick, onDownloadsClick = onDownloadsClick, onAccountClick = onAccountClick, @@ -456,6 +462,9 @@ private fun MobileSettingsScreen( SettingsPage.SupportersContributors -> supportersContributorsContent( isTablet = false, ) + SettingsPage.LicensesAttributions -> licensesAttributionsContent( + isTablet = false, + ) SettingsPage.Playback -> playbackSettingsContent( isTablet = false, showLoadingOverlay = showLoadingOverlay, @@ -635,6 +644,7 @@ private fun TabletSettingsScreen( onSwitchProfile: (() -> Unit)? = null, onDownloadsClick: () -> Unit = {}, onSupportersContributorsClick: () -> Unit = {}, + onLicensesAttributionsClick: () -> Unit = {}, onCheckForUpdatesClick: (() -> Unit)? = null, onCollectionsClick: () -> Unit = {}, ) { @@ -792,6 +802,7 @@ private fun TabletSettingsScreen( onIntegrationsClick = { openInlinePage(SettingsPage.Integrations) }, onTraktClick = { openInlinePage(SettingsPage.TraktAuthentication) }, onSupportersContributorsClick = { openInlinePage(SettingsPage.SupportersContributors) }, + onLicensesAttributionsClick = { openInlinePage(SettingsPage.LicensesAttributions) }, onCheckForUpdatesClick = onCheckForUpdatesClick, onDownloadsClick = onDownloadsClick, onAccountClick = { openInlinePage(SettingsPage.Account) }, @@ -808,6 +819,9 @@ private fun TabletSettingsScreen( SettingsPage.SupportersContributors -> supportersContributorsContent( isTablet = true, ) + SettingsPage.LicensesAttributions -> licensesAttributionsContent( + isTablet = true, + ) SettingsPage.Playback -> playbackSettingsContent( isTablet = true, showLoadingOverlay = showLoadingOverlay, 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 bdacf0de..1f3bafee 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 @@ -21,6 +21,7 @@ import androidx.compose.material.icons.rounded.Favorite import androidx.compose.material.icons.rounded.Hub import androidx.compose.material.icons.rounded.Home import androidx.compose.material.icons.rounded.Language +import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.Link import androidx.compose.material.icons.rounded.Notifications import androidx.compose.material.icons.rounded.Palette @@ -94,6 +95,7 @@ internal fun settingsSearchEntries( val integrationsPage = stringResource(Res.string.compose_settings_page_integrations) val notificationsPage = stringResource(Res.string.compose_settings_page_notifications) val supportersPage = stringResource(Res.string.compose_settings_page_supporters_contributors) + val licensesPage = stringResource(Res.string.compose_settings_page_licenses_attributions) val homeLayoutPage = stringResource(Res.string.compose_settings_page_homescreen) val detailPage = stringResource(Res.string.compose_settings_page_meta_screen) val continueWatchingPage = stringResource(Res.string.compose_settings_page_continue_watching) @@ -248,6 +250,46 @@ internal fun settingsSearchEntries( category = aboutCategory, icon = Icons.Rounded.Favorite, ) + addPage( + page = SettingsPage.LicensesAttributions, + key = "licenses-attributions", + title = licensesPage, + description = stringResource(Res.string.about_licenses_attributions_subtitle), + category = aboutCategory, + icon = Icons.Rounded.Info, + ) + listOf( + 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("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)), + PlaybackSearchRow( + if (isIos) "mpvkit-license" else "exoplayer-license", + if (isIos) { + stringResource(Res.string.settings_licenses_attributions_mpvkit_title) + } else { + stringResource(Res.string.settings_licenses_attributions_exoplayer_title) + }, + if (isIos) { + stringResource(Res.string.settings_licenses_attributions_mpvkit_license) + } else { + stringResource(Res.string.settings_licenses_attributions_exoplayer_license) + }, + ), + ).forEach { row -> + addRow( + page = SettingsPage.LicensesAttributions, + key = row.key, + title = row.title, + description = row.description, + pageLabel = licensesPage, + section = stringResource(Res.string.compose_settings_root_about_section), + category = aboutCategory, + icon = Icons.Rounded.Info, + ) + } if (checkForUpdatesAvailable) { add( key = "check-updates", diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt index dfbe18a4..6aa94391 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt @@ -3,6 +3,7 @@ package com.nuvio.app.features.settings import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import nuvio.composeapp.generated.resources.Res +import nuvio.composeapp.generated.resources.introdb_favicon import nuvio.composeapp.generated.resources.mdblist_logo import nuvio.composeapp.generated.resources.rating_tmdb import nuvio.composeapp.generated.resources.trakt_tv_favicon @@ -14,4 +15,5 @@ internal actual fun integrationLogoPainter(logo: IntegrationLogo): Painter = IntegrationLogo.Tmdb -> painterResource(Res.drawable.rating_tmdb) IntegrationLogo.Trakt -> painterResource(Res.drawable.trakt_tv_favicon) IntegrationLogo.MdbList -> painterResource(Res.drawable.mdblist_logo) + IntegrationLogo.IntroDb -> painterResource(Res.drawable.introdb_favicon) }