mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-03 08:49:07 +00:00
feat: add moreLikeThis and collection features to MetaDetails, enhancing recommendations and collections display in detail screens
This commit is contained in:
parent
a4a4f3ced4
commit
b82d9caced
7 changed files with 294 additions and 16 deletions
|
|
@ -96,6 +96,7 @@ import com.nuvio.app.features.streams.StreamContextStore
|
|||
import com.nuvio.app.features.streams.StreamLinkCacheRepository
|
||||
import com.nuvio.app.features.streams.StreamsRepository
|
||||
import com.nuvio.app.features.streams.StreamsScreen
|
||||
import com.nuvio.app.features.tmdb.TmdbService
|
||||
import com.nuvio.app.features.player.PlayerSettingsRepository
|
||||
import com.nuvio.app.features.watched.WatchedRepository
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingItem
|
||||
|
|
@ -480,6 +481,27 @@ private fun MainAppContent(
|
|||
navController.popBackStack()
|
||||
},
|
||||
onPlay = onPlay,
|
||||
onOpenMeta = { preview ->
|
||||
coroutineScope.launch {
|
||||
val resolvedId = if (preview.id.startsWith("tmdb:")) {
|
||||
val tmdbId = preview.id.removePrefix("tmdb:").toIntOrNull()
|
||||
tmdbId?.let {
|
||||
TmdbService.tmdbToImdb(
|
||||
tmdbId = it,
|
||||
mediaType = preview.type,
|
||||
)
|
||||
} ?: preview.id
|
||||
} else {
|
||||
preview.id
|
||||
}
|
||||
navController.navigate(
|
||||
DetailRoute(
|
||||
type = preview.type,
|
||||
id = resolvedId,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ fun <T> NuvioShelfSection(
|
|||
headerHorizontalPadding: Dp = 0.dp,
|
||||
rowContentPadding: PaddingValues = PaddingValues(0.dp),
|
||||
itemSpacing: Dp = 10.dp,
|
||||
showHeaderAccent: Boolean = true,
|
||||
onViewAllClick: (() -> Unit)? = null,
|
||||
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
|
||||
key: ((T) -> Any)? = null,
|
||||
|
|
@ -64,6 +65,7 @@ fun <T> NuvioShelfSection(
|
|||
NuvioShelfSectionHeader(
|
||||
title = title,
|
||||
modifier = Modifier.padding(horizontal = headerHorizontalPadding),
|
||||
showAccent = showHeaderAccent,
|
||||
onViewAllClick = onViewAllClick,
|
||||
viewAllPillSize = viewAllPillSize,
|
||||
)
|
||||
|
|
@ -99,7 +101,7 @@ fun NuvioPosterCard(
|
|||
onLongClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.width(110.dp),
|
||||
modifier = modifier.width(shape.cardWidth),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Box(
|
||||
|
|
@ -161,6 +163,7 @@ fun NuvioPosterCard(
|
|||
private fun NuvioShelfSectionHeader(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
showAccent: Boolean = true,
|
||||
onViewAllClick: (() -> Unit)? = null,
|
||||
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
|
||||
) {
|
||||
|
|
@ -179,16 +182,18 @@ private fun NuvioShelfSectionHeader(
|
|||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp)
|
||||
.width(60.dp)
|
||||
.height(4.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
),
|
||||
)
|
||||
if (showAccent) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp)
|
||||
.width(60.dp)
|
||||
.height(4.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (onViewAllClick != null) {
|
||||
NuvioViewAllPill(
|
||||
|
|
@ -245,6 +250,13 @@ private val NuvioPosterShape.aspectRatio: Float
|
|||
NuvioPosterShape.Landscape -> 1.77f
|
||||
}
|
||||
|
||||
private val NuvioPosterShape.cardWidth: Dp
|
||||
get() = when (this) {
|
||||
NuvioPosterShape.Poster -> 110.dp
|
||||
NuvioPosterShape.Square -> 110.dp
|
||||
NuvioPosterShape.Landscape -> 180.dp
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
internal fun Modifier.posterCardClickable(
|
||||
onClick: (() -> Unit)?,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nuvio.app.features.details
|
||||
|
||||
import com.nuvio.app.features.home.MetaPreview
|
||||
import com.nuvio.app.features.streams.StreamItem
|
||||
|
||||
data class MetaDetails(
|
||||
|
|
@ -26,6 +27,9 @@ data class MetaDetails(
|
|||
val language: String? = null,
|
||||
val website: String? = null,
|
||||
val hasScheduledVideos: Boolean = false,
|
||||
val moreLikeThis: List<MetaPreview> = emptyList(),
|
||||
val collectionName: String? = null,
|
||||
val collectionItems: List<MetaPreview> = emptyList(),
|
||||
val links: List<MetaLink> = emptyList(),
|
||||
val videos: List<MetaVideo> = emptyList(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,9 +42,11 @@ import com.nuvio.app.features.details.components.DetailCastSection
|
|||
import com.nuvio.app.features.details.components.DetailFloatingHeader
|
||||
import com.nuvio.app.features.details.components.DetailHero
|
||||
import com.nuvio.app.features.details.components.DetailMetaInfo
|
||||
import com.nuvio.app.features.details.components.DetailPosterRailSection
|
||||
import com.nuvio.app.features.details.components.DetailProductionSection
|
||||
import com.nuvio.app.features.details.components.DetailSeriesContent
|
||||
import com.nuvio.app.features.details.components.EpisodeWatchedActionSheet
|
||||
import com.nuvio.app.features.home.MetaPreview
|
||||
import com.nuvio.app.features.library.LibraryRepository
|
||||
import com.nuvio.app.features.library.toLibraryItem
|
||||
import com.nuvio.app.features.watched.WatchedRepository
|
||||
|
|
@ -62,6 +64,7 @@ fun MetaDetailsScreen(
|
|||
id: String,
|
||||
onBack: () -> Unit,
|
||||
onPlay: ((type: String, videoId: String, parentMetaId: String, parentMetaType: String, title: String, logo: String?, poster: String?, background: String?, seasonNumber: Int?, episodeNumber: Int?, episodeTitle: String?, episodeThumbnail: String?, pauseDescription: String?, resumePositionMs: Long?) -> Unit)? = null,
|
||||
onOpenMeta: ((MetaPreview) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val uiState by MetaDetailsRepository.uiState.collectAsStateWithLifecycle()
|
||||
|
|
@ -175,6 +178,12 @@ fun MetaDetailsScreen(
|
|||
meta.country != null ||
|
||||
meta.language != null
|
||||
}
|
||||
val hasCollectionSection = remember(meta) {
|
||||
meta.collectionName != null && meta.collectionItems.isNotEmpty()
|
||||
}
|
||||
val hasMoreLikeThisSection = remember(meta) {
|
||||
meta.moreLikeThis.isNotEmpty()
|
||||
}
|
||||
val playButtonLabel = remember(movieProgress, seriesAction, meta.type, hasEpisodes) {
|
||||
when {
|
||||
(meta.type == "series" || hasEpisodes) && seriesAction != null ->
|
||||
|
|
@ -327,6 +336,24 @@ fun MetaDetailsScreen(
|
|||
DetailAdditionalInfoSection(meta = meta)
|
||||
}
|
||||
|
||||
if (!hasEpisodes && hasCollectionSection) {
|
||||
DetailPosterRailSection(
|
||||
title = meta.collectionName.orEmpty(),
|
||||
items = meta.collectionItems,
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
onPosterClick = onOpenMeta,
|
||||
)
|
||||
}
|
||||
|
||||
if (hasMoreLikeThisSection) {
|
||||
DetailPosterRailSection(
|
||||
title = "More Like This",
|
||||
items = meta.moreLikeThis,
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
onPosterClick = onOpenMeta,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp + nuvioPlatformExtraBottomPadding))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.nuvio.app.features.details.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.core.ui.NuvioShelfSection
|
||||
import com.nuvio.app.features.home.MetaPreview
|
||||
import com.nuvio.app.features.home.components.HomePosterCard
|
||||
import com.nuvio.app.features.home.stableKey
|
||||
import com.nuvio.app.features.watching.application.WatchingState
|
||||
|
||||
@Composable
|
||||
fun DetailPosterRailSection(
|
||||
title: String,
|
||||
items: List<MetaPreview>,
|
||||
watchedKeys: Set<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
onPosterClick: ((MetaPreview) -> Unit)? = null,
|
||||
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
|
||||
) {
|
||||
if (items.isEmpty()) return
|
||||
|
||||
NuvioShelfSection(
|
||||
title = title,
|
||||
entries = items,
|
||||
modifier = modifier,
|
||||
rowContentPadding = PaddingValues(0.dp),
|
||||
showHeaderAccent = false,
|
||||
key = { item -> item.stableKey() },
|
||||
) { item ->
|
||||
HomePosterCard(
|
||||
item = item,
|
||||
isWatched = WatchingState.isPosterWatched(
|
||||
watchedKeys = watchedKeys,
|
||||
item = item,
|
||||
),
|
||||
onClick = onPosterClick?.let { { it(item) } },
|
||||
onLongClick = onPosterLongClick?.let { { it(item) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ internal fun LazyListScope.tmdbSettingsContent(
|
|||
TmdbToggleRow(
|
||||
isTablet = isTablet,
|
||||
title = "More like this",
|
||||
description = "Reserved for the upcoming TMDB recommendation rail port.",
|
||||
description = "Show TMDB recommendations at the bottom of detail pages.",
|
||||
checked = settings.useMoreLikeThis,
|
||||
enabled = settings.enabled,
|
||||
onCheckedChange = TmdbSettingsRepository::setUseMoreLikeThis,
|
||||
|
|
@ -139,7 +139,7 @@ internal fun LazyListScope.tmdbSettingsContent(
|
|||
TmdbToggleRow(
|
||||
isTablet = isTablet,
|
||||
title = "Collections",
|
||||
description = "Reserved for the upcoming TMDB collection rail port.",
|
||||
description = "Show franchise and collection rails for movies when TMDB provides them.",
|
||||
checked = settings.useCollections,
|
||||
enabled = settings.enabled,
|
||||
onCheckedChange = TmdbSettingsRepository::setUseCollections,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import com.nuvio.app.features.details.MetaCompany
|
|||
import com.nuvio.app.features.details.MetaDetails
|
||||
import com.nuvio.app.features.details.MetaPerson
|
||||
import com.nuvio.app.features.details.MetaVideo
|
||||
import com.nuvio.app.features.home.MetaPreview
|
||||
import com.nuvio.app.features.home.PosterShape
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -21,6 +23,8 @@ object TmdbMetadataService {
|
|||
|
||||
private val enrichmentCache = mutableMapOf<String, TmdbEnrichment>()
|
||||
private val episodeCache = mutableMapOf<String, Map<Pair<Int, Int>, TmdbEpisodeEnrichment>>()
|
||||
private val moreLikeThisCache = mutableMapOf<String, List<MetaPreview>>()
|
||||
private val collectionCache = mutableMapOf<String, Pair<String?, List<MetaPreview>>>()
|
||||
|
||||
suspend fun enrichMeta(
|
||||
meta: MetaDetails,
|
||||
|
|
@ -41,6 +45,7 @@ object TmdbMetadataService {
|
|||
tmdbId = tmdbId,
|
||||
mediaType = tmdbType,
|
||||
language = settings.language,
|
||||
settings = settings,
|
||||
)
|
||||
}
|
||||
val episodeDeferred = if (needsEpisodes) {
|
||||
|
|
@ -142,6 +147,17 @@ object TmdbMetadataService {
|
|||
)
|
||||
}
|
||||
|
||||
if (enrichment != null && settings.useMoreLikeThis) {
|
||||
updated = updated.copy(moreLikeThis = enrichment.moreLikeThis)
|
||||
}
|
||||
|
||||
if (enrichment != null && settings.useCollections) {
|
||||
updated = updated.copy(
|
||||
collectionName = enrichment.collectionName,
|
||||
collectionItems = enrichment.collectionItems,
|
||||
)
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
|
|
@ -149,6 +165,7 @@ object TmdbMetadataService {
|
|||
tmdbId: String,
|
||||
mediaType: String,
|
||||
language: String,
|
||||
settings: TmdbSettings,
|
||||
): TmdbEnrichment? = withContext(Dispatchers.Default) {
|
||||
val normalizedLanguage = normalizeTmdbLanguage(language)
|
||||
val cacheKey = "$tmdbId:$mediaType:$normalizedLanguage"
|
||||
|
|
@ -191,11 +208,22 @@ object TmdbMetadataService {
|
|||
)?.results.orEmpty().selectMovieAgeRating(normalizedLanguage)
|
||||
}
|
||||
}
|
||||
val moreLikeThis = async {
|
||||
if (settings.useMoreLikeThis && (mediaType == "movie" || mediaType == "tv")) {
|
||||
fetchMoreLikeThis(
|
||||
tmdbId = numericId,
|
||||
mediaType = mediaType,
|
||||
language = normalizedLanguage,
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
Quadruple(
|
||||
first = details.await(),
|
||||
second = credits.await(),
|
||||
third = images.await(),
|
||||
fourth = ageRating.await(),
|
||||
fourth = Pair(ageRating.await(), moreLikeThis.await()),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +251,7 @@ object TmdbMetadataService {
|
|||
releaseInfo = releaseInfo,
|
||||
rating = details.voteAverage,
|
||||
runtimeMinutes = details.runtime ?: details.episodeRunTime.firstOrNull(),
|
||||
ageRating = response.fourth,
|
||||
ageRating = response.fourth.first,
|
||||
status = details.status?.trim()?.takeIf(String::isNotBlank),
|
||||
countries = details.productionCountries
|
||||
.mapNotNull { it.iso31661?.trim()?.takeIf(String::isNotBlank) }
|
||||
|
|
@ -231,6 +259,16 @@ object TmdbMetadataService {
|
|||
language = details.originalLanguage?.trim()?.takeIf(String::isNotBlank),
|
||||
productionCompanies = details.productionCompanies.mapNotNull { it.toMetaCompany() },
|
||||
networks = details.networks.mapNotNull { it.toMetaCompany() },
|
||||
collectionName = details.belongsToCollection?.name?.trim()?.takeIf(String::isNotBlank),
|
||||
collectionItems = if (settings.useCollections && details.belongsToCollection?.id != null) {
|
||||
fetchCollection(
|
||||
collectionId = details.belongsToCollection.id,
|
||||
language = normalizedLanguage,
|
||||
).second
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
moreLikeThis = response.fourth.second,
|
||||
)
|
||||
|
||||
if (!enrichment.hasContent()) return@withContext null
|
||||
|
|
@ -293,6 +331,89 @@ object TmdbMetadataService {
|
|||
log.w { "TMDB request failed for $endpoint: ${error.message}" }
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private suspend fun fetchMoreLikeThis(
|
||||
tmdbId: Int,
|
||||
mediaType: String,
|
||||
language: String,
|
||||
): List<MetaPreview> {
|
||||
val cacheKey = "$tmdbId:$mediaType:$language:recommendations"
|
||||
moreLikeThisCache[cacheKey]?.let { return it }
|
||||
|
||||
val response = fetch<TmdbRecommendationResponse>(
|
||||
endpoint = "$mediaType/$tmdbId/recommendations",
|
||||
query = mapOf("language" to language),
|
||||
) ?: return emptyList()
|
||||
|
||||
val items = response.results
|
||||
.filter { it.id > 0 }
|
||||
.mapNotNull { recommendation ->
|
||||
val inferredType = when (recommendation.mediaType?.lowercase()) {
|
||||
"tv" -> "series"
|
||||
"movie" -> "movie"
|
||||
else -> if (mediaType == "tv") "series" else "movie"
|
||||
}
|
||||
val title = recommendation.title
|
||||
?.trim()
|
||||
?.takeIf(String::isNotBlank)
|
||||
?: recommendation.name?.trim()?.takeIf(String::isNotBlank)
|
||||
?: recommendation.originalTitle?.trim()?.takeIf(String::isNotBlank)
|
||||
?: recommendation.originalName?.trim()?.takeIf(String::isNotBlank)
|
||||
?: return@mapNotNull null
|
||||
|
||||
MetaPreview(
|
||||
id = "tmdb:${recommendation.id}",
|
||||
type = inferredType,
|
||||
name = title,
|
||||
poster = buildImageUrl(recommendation.posterPath, "w500")
|
||||
?: buildImageUrl(recommendation.backdropPath, "w780"),
|
||||
banner = buildImageUrl(recommendation.backdropPath, "w1280"),
|
||||
posterShape = PosterShape.Poster,
|
||||
description = recommendation.overview?.trim()?.takeIf(String::isNotBlank),
|
||||
releaseInfo = (recommendation.releaseDate ?: recommendation.firstAirDate)?.take(4),
|
||||
imdbRating = recommendation.voteAverage?.formatRating(),
|
||||
)
|
||||
}
|
||||
.take(12)
|
||||
|
||||
moreLikeThisCache[cacheKey] = items
|
||||
return items
|
||||
}
|
||||
|
||||
private suspend fun fetchCollection(
|
||||
collectionId: Int,
|
||||
language: String,
|
||||
): Pair<String?, List<MetaPreview>> {
|
||||
val cacheKey = "$collectionId:$language:collection"
|
||||
collectionCache[cacheKey]?.let { return it }
|
||||
|
||||
val response = fetch<TmdbCollectionResponse>(
|
||||
endpoint = "collection/$collectionId",
|
||||
query = mapOf("language" to language),
|
||||
) ?: return null to emptyList()
|
||||
|
||||
val items = response.parts
|
||||
.sortedBy { it.releaseDate ?: "9999" }
|
||||
.mapNotNull { part ->
|
||||
val title = part.title?.trim()?.takeIf(String::isNotBlank) ?: return@mapNotNull null
|
||||
MetaPreview(
|
||||
id = "tmdb:${part.id}",
|
||||
type = "movie",
|
||||
name = title,
|
||||
poster = buildImageUrl(part.backdropPath, "w780")
|
||||
?: buildImageUrl(part.posterPath, "w500"),
|
||||
banner = buildImageUrl(part.backdropPath, "w1280"),
|
||||
posterShape = PosterShape.Landscape,
|
||||
description = part.overview?.trim()?.takeIf(String::isNotBlank),
|
||||
releaseInfo = part.releaseDate?.take(4),
|
||||
imdbRating = part.voteAverage?.formatRating(),
|
||||
)
|
||||
}
|
||||
|
||||
val result = response.name?.trim()?.takeIf(String::isNotBlank) to items
|
||||
collectionCache[cacheKey] = result
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
internal data class TmdbEnrichment(
|
||||
|
|
@ -314,6 +435,9 @@ internal data class TmdbEnrichment(
|
|||
val language: String?,
|
||||
val productionCompanies: List<MetaCompany>,
|
||||
val networks: List<MetaCompany>,
|
||||
val collectionName: String?,
|
||||
val collectionItems: List<MetaPreview>,
|
||||
val moreLikeThis: List<MetaPreview>,
|
||||
) {
|
||||
fun hasContent(): Boolean =
|
||||
localizedTitle != null ||
|
||||
|
|
@ -333,7 +457,9 @@ internal data class TmdbEnrichment(
|
|||
countries.isNotEmpty() ||
|
||||
language != null ||
|
||||
productionCompanies.isNotEmpty() ||
|
||||
networks.isNotEmpty()
|
||||
networks.isNotEmpty() ||
|
||||
collectionItems.isNotEmpty() ||
|
||||
moreLikeThis.isNotEmpty()
|
||||
}
|
||||
|
||||
internal data class TmdbEpisodeEnrichment(
|
||||
|
|
@ -585,6 +711,7 @@ private data class TmdbDetailsResponse(
|
|||
val genres: List<TmdbNamedItem> = emptyList(),
|
||||
@SerialName("production_companies") val productionCompanies: List<TmdbCompany> = emptyList(),
|
||||
val networks: List<TmdbCompany> = emptyList(),
|
||||
@SerialName("belongs_to_collection") val belongsToCollection: TmdbCollectionRef? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -672,6 +799,50 @@ private data class TmdbCompany(
|
|||
@SerialName("logo_path") val logoPath: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class TmdbCollectionRef(
|
||||
val id: Int? = null,
|
||||
val name: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class TmdbRecommendationResponse(
|
||||
val results: List<TmdbRecommendationItem> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class TmdbRecommendationItem(
|
||||
val id: Int,
|
||||
val title: String? = null,
|
||||
val name: String? = null,
|
||||
@SerialName("original_title") val originalTitle: String? = null,
|
||||
@SerialName("original_name") val originalName: String? = null,
|
||||
@SerialName("poster_path") val posterPath: String? = null,
|
||||
@SerialName("backdrop_path") val backdropPath: String? = null,
|
||||
val overview: String? = null,
|
||||
@SerialName("release_date") val releaseDate: String? = null,
|
||||
@SerialName("first_air_date") val firstAirDate: String? = null,
|
||||
@SerialName("vote_average") val voteAverage: Double? = null,
|
||||
@SerialName("media_type") val mediaType: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class TmdbCollectionResponse(
|
||||
val name: String? = null,
|
||||
val parts: List<TmdbCollectionPart> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class TmdbCollectionPart(
|
||||
val id: Int,
|
||||
val title: String? = null,
|
||||
@SerialName("poster_path") val posterPath: String? = null,
|
||||
@SerialName("backdrop_path") val backdropPath: String? = null,
|
||||
val overview: String? = null,
|
||||
@SerialName("release_date") val releaseDate: String? = null,
|
||||
@SerialName("vote_average") val voteAverage: Double? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class TmdbSeasonDetailsResponse(
|
||||
val episodes: List<TmdbEpisodeResponse> = emptyList(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue