move TMDBApiCOntroller to Fuel and kotlinx.serialization

* add year and maturityRatings to MediaFragment
* don't show season selection if only one season is present
This commit is contained in:
Jannik 2021-12-31 16:03:15 +01:00
parent 27e7f2a249
commit 554af530e3
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
9 changed files with 261 additions and 202 deletions

View File

@ -46,7 +46,7 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'

View File

@ -76,6 +76,8 @@ object Crunchyroll {
): Result<FuelJson, FuelError> = coroutineScope {
val path = if (url.isEmpty()) "$baseUrl$endpoint" else url
// TODO before sending a request, make sure the accessToken is not expired
return@coroutineScope (Dispatchers.IO) {
val (request, response, result) = Fuel.get(path, params)
.header("Authorization", "$tokenType $accessToken")
@ -182,7 +184,6 @@ object Crunchyroll {
val result = request(episodesEndpoint, parameters)
return result.component1()?.obj()?.let {
println(it)
json.decodeFromString(it.toString())
} ?: NoneSeasons
}
@ -200,7 +201,6 @@ object Crunchyroll {
val result = request(episodesEndpoint, parameters)
return result.component1()?.obj()?.let {
println(it)
json.decodeFromString(it.toString())
} ?: NoneEpisodes
}
@ -209,7 +209,6 @@ object Crunchyroll {
val result = request("", url = url)
return result.component1()?.obj()?.let {
println(it)
json.decodeFromString(it.toString())
} ?: NonePlayback
}

View File

@ -64,12 +64,13 @@ data class Poster(val height: Int, val width: Int, val source: String, val type:
*/
@Serializable
data class Series(
val id: String,
val title: String,
val description: String,
val images: Images
@SerialName("id") val id: String,
@SerialName("title") val title: String,
@SerialName("description") val description: String,
@SerialName("images") val images: Images,
@SerialName("maturity_ratings") val maturityRatings: List<String>
)
val NoneSeries = Series("", "", "", Images(listOf(), listOf()))
val NoneSeries = Series("", "", "", Images(emptyList(), emptyList()), emptyList())
/**
@ -77,8 +78,8 @@ val NoneSeries = Series("", "", "", Images(listOf(), listOf()))
*/
@Serializable
data class Seasons(
val total: Int,
val items: List<Season>
@SerialName("total") val total: Int,
@SerialName("items") val items: List<Season>
) {
fun getPreferredSeason(local: Locale): Season {
// try to get the the first seasons which matches the preferred local
@ -111,14 +112,17 @@ data class Season(
)
val NoneSeasons = Seasons(0, listOf())
val NoneSeason = Season("", "", "", 0, false, false)
val NoneSeason = Season("", "", "", 0, isSubbed = false, isDubbed = false)
/**
* Episodes data type
*/
@Serializable
data class Episodes(val total: Int, val items: List<Episode>)
data class Episodes(
@SerialName("total") val total: Int,
@SerialName("items") val items: List<Episode>
)
@Serializable
data class Episode(

View File

@ -24,10 +24,9 @@ import org.mosad.teapod.parser.crunchyroll.Item
import org.mosad.teapod.parser.crunchyroll.NoneItem
import org.mosad.teapod.ui.activity.main.MainActivity
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.tmdb.TMDBMovie
import org.mosad.teapod.util.tmdb.TMDBApiController
import org.mosad.teapod.util.tmdb.TMDBMovie
import org.mosad.teapod.util.tmdb.TMDBTVShow
/**
* The media detail fragment.
@ -48,6 +47,7 @@ class MediaFragment(private val mediaIdStr: String, mediaCr: Item = NoneItem) :
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.frameLoading.visibility = View.VISIBLE
@ -89,9 +89,9 @@ class MediaFragment(private val mediaIdStr: String, mediaCr: Item = NoneItem) :
*/
private fun updateGUI() = with(model) {
// generic gui
val backdropUrl = tmdbResult?.backdropPath?.let { TMDBApiController.imageUrl + it }
val backdropUrl = tmdbResult.backdropPath?.let { TMDBApiController.imageUrl + it }
?: seriesCrunchy.images.poster_wide[0][2].source
val posterUrl = tmdbResult?.posterPath?.let { TMDBApiController.imageUrl + it }
val posterUrl = tmdbResult.posterPath?.let { TMDBApiController.imageUrl + it }
?: seriesCrunchy.images.poster_tall[0][2].source
// load poster and backdrop
@ -103,9 +103,14 @@ class MediaFragment(private val mediaIdStr: String, mediaCr: Item = NoneItem) :
.into(binding.imageBackdrop)
binding.textTitle.text = seriesCrunchy.title
//binding.textYear.text = media.year.toString() // TODO get from tmdb
//binding.textAge.text = media.age.toString() // TODO get from tmdb
binding.textOverview.text = seriesCrunchy.description
binding.textAge.text = seriesCrunchy.maturityRatings.firstOrNull()
binding.textYear.text = when(tmdbResult) {
is TMDBTVShow -> (tmdbResult as TMDBTVShow).firstAirDate.substring(0, 4)
is TMDBMovie -> (tmdbResult as TMDBMovie).releaseDate.substring(0, 4)
else -> ""
}
// TODO set "my list" indicator
// if (StorageController.myList.contains(media.aodId)) {
@ -119,8 +124,7 @@ class MediaFragment(private val mediaIdStr: String, mediaCr: Item = NoneItem) :
fragments.clear()
pagerAdapter.notifyItemRangeRemoved(0, fragmentsSize)
// add the episodes fragment (as tab)
// add the episodes fragment (as tab). Note: Movies are tv shows!
MediaFragmentEpisodes().also {
fragments.add(it)
pagerAdapter.notifyItemInserted(fragments.indexOf(it))
@ -128,6 +132,33 @@ class MediaFragment(private val mediaIdStr: String, mediaCr: Item = NoneItem) :
// TODO reimplement via tmdb/metaDB
// specific gui
when (tmdbResult) {
is TMDBTVShow -> {
// episodes count
binding.textEpisodesOrRuntime.text = resources.getQuantityString(
R.plurals.text_episodes_count,
episodesCrunchy.total,
episodesCrunchy.total
)
}
is TMDBMovie -> {
val tmdbMovie = (tmdbResult as TMDBMovie?)
if (tmdbMovie?.runtime != null) {
binding.textEpisodesOrRuntime.text = resources.getQuantityString(
R.plurals.text_runtime,
tmdbMovie.runtime,
tmdbMovie.runtime
)
} else {
binding.textEpisodesOrRuntime.visibility = View.GONE
}
}
else -> {
println("else")
}
}
// if (mediaCrunchy.type == MediaType.TVSHOW.str) {
// // TODO get next episode
//// nextEpisodeId = media.playlist.firstOrNull{ !it.watched }?.mediaId

View File

@ -31,7 +31,7 @@ class MediaFragmentEpisodes : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapterRecEpisodes = EpisodeItemAdapter(model.currentEpisodesCrunchy, model.tmdbTVSeason?.episodes)
adapterRecEpisodes = EpisodeItemAdapter(model.currentEpisodesCrunchy, model.tmdbTVSeason.episodes)
binding.recyclerEpisodes.adapter = adapterRecEpisodes
// set onItemClick, adapter is initialized
@ -39,10 +39,14 @@ class MediaFragmentEpisodes : Fragment() {
playEpisode(seasonId, episodeId)
}
// TODO don't show selection if only one season is present
binding.buttonSeasonSelection.text = model.currentSeasonCrunchy.title
binding.buttonSeasonSelection.setOnClickListener { v ->
showSeasonSelection(v)
// don't show season selection if only one season is present
if (model.seasonsCrunchy.total < 2) {
binding.buttonSeasonSelection.visibility = View.GONE
} else {
binding.buttonSeasonSelection.text = model.currentSeasonCrunchy.title
binding.buttonSeasonSelection.setOnClickListener { v ->
showSeasonSelection(v)
}
}
}

View File

@ -1,18 +1,15 @@
package org.mosad.teapod.ui.activity.main.viewmodel
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi
import org.mosad.teapod.parser.crunchyroll.*
import org.mosad.teapod.preferences.Preferences
import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.Meta
import org.mosad.teapod.util.tmdb.TMDBApiController
import org.mosad.teapod.util.tmdb.TMDBResult
import org.mosad.teapod.util.tmdb.TMDBTVSeason
import org.mosad.teapod.util.tmdb.*
/**
* handle media, next ep and tmdb
@ -22,7 +19,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
// var mediaCrunchy = NoneItem
// internal set
var seriesCrunchy = NoneSeries // TODO it seems movies also series?
var seriesCrunchy = NoneSeries // movies are also series
internal set
var seasonsCrunchy = NoneSeasons
internal set
@ -32,9 +29,9 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
internal set
val currentEpisodesCrunchy = arrayListOf<Episode>() // used for EpisodeItemAdapter (easier updates)
var tmdbResult: TMDBResult? = null // TODO rename
var tmdbResult: TMDBResult = NoneTMDB // TODO rename
internal set
var tmdbTVSeason: TMDBTVSeason? =null
var tmdbTVSeason: TMDBTVSeason = NoneTMDBTVSeason
internal set
var mediaMeta: Meta? = null
internal set
@ -42,9 +39,8 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
/**
* @param crunchyId the crunchyroll series id
*/
suspend fun loadCrunchy(crunchyId: String) {
val tmdbApiController = TMDBApiController()
suspend fun loadCrunchy(crunchyId: String) {
// load series and seasons info in parallel
listOf(
viewModelScope.launch { seriesCrunchy = Crunchyroll.series(crunchyId) },
@ -54,6 +50,8 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
println("series: $seriesCrunchy")
println("seasons: $seasonsCrunchy")
// TODO load episodes, metaDB and tmdb in parallel
// load the preferred season (preferred language, language per season, not per stream)
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocal)
episodesCrunchy = Crunchyroll.episodes(currentSeasonCrunchy.id)
@ -62,18 +60,17 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
println("episodes: $episodesCrunchy")
// TODO check if metaDB knows the title
// use tmdb search to get media info TODO media type is hardcoded, use episodeNumber? (if null it should be a movie)
mediaMeta = null // set mediaMeta to null, if metaDB doesn't know the media
val tmdbId = tmdbApiController.search(seriesCrunchy.title, MediaType.TVSHOW)
tmdbResult = when (MediaType.TVSHOW) {
MediaType.MOVIE -> tmdbApiController.getMovieDetails(tmdbId)
MediaType.TVSHOW -> tmdbApiController.getTVShowDetails(tmdbId)
else -> null
}
// use tmdb search to get media info
loadTmdbInfo()
}
/**
* Set currentSeasonCrunchy based on the season id. Also set the new seasons episodes.
*
* @param seasonId the id of the season to set
*/
suspend fun setCurrentSeason(seasonId: String) {
// return if the id hasn't changed (performance)
if (currentSeasonCrunchy.id == seasonId) return
@ -90,49 +87,33 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
}
/**
* set media, tmdb and nextEpisode
* Load the tmdb info for the selected media.
* The TMDB search return a media type, use this to get the details (movie/tv show and season)
*/
// suspend fun loadAoD(aodId: Int) {
// val tmdbApiController = TMDBApiController()
// media = AoDParser.getMediaById(aodId)
//
// // check if metaDB knows the title
// val tmdbId: Int = if (MetaDBController.mediaList.media.contains(aodId)) {
// // load media info from metaDB
// val metaDB = MetaDBController()
// mediaMeta = when (media.type) {
// MediaType.MOVIE -> metaDB.getMovieMetadata(media.aodId)
// MediaType.TVSHOW -> metaDB.getTVShowMetadata(media.aodId)
// else -> null
// }
//
// mediaMeta?.tmdbId ?: -1
// } else {
// // use tmdb search to get media info
// mediaMeta = null // set mediaMeta to null, if metaDB doesn't know the media
// tmdbApiController.search(stripTitleInfo(media.title), media.type)
// }
//
// tmdbResult = when (media.type) {
// MediaType.MOVIE -> tmdbApiController.getMovieDetails(tmdbId)
// MediaType.TVSHOW -> tmdbApiController.getTVShowDetails(tmdbId)
// else -> null
// }
//
// // get season info, if metaDB knows the tv show
// tmdbTVSeason = if (media.type == MediaType.TVSHOW && mediaMeta != null) {
// val tvShowMeta = mediaMeta as TVShowMeta
// tmdbApiController.getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber)
// } else {
// null
// }
//
// if (media.type == MediaType.TVSHOW) {
// //nextEpisode = media.episodes.firstOrNull{ !it.watched } ?: media.episodes.first()
// nextEpisodeId = media.playlist.firstOrNull { !it.watched }?.mediaId
// ?: media.playlist.first().mediaId
// }
// }
@ExperimentalSerializationApi
suspend fun loadTmdbInfo() {
val tmdbApiController = TMDBApiController()
val tmdbSearchResult = tmdbApiController.searchMulti(seriesCrunchy.title)
println(tmdbSearchResult)
tmdbResult = if (tmdbSearchResult.results.isNotEmpty()) {
val result = tmdbSearchResult.results.first()
when (result.mediaType) {
"movie" -> tmdbApiController.getMovieDetails(result.id)
"tv" -> tmdbApiController.getTVShowDetails(result.id)
else -> NoneTMDB
}
} else NoneTMDB
println(tmdbResult)
// currently not used
// tmdbTVSeason = if (tmdbResult is TMDBTVShow) {
// tmdbApiController.getTVSeasonDetails(tmdbResult.id, 0)
// } else NoneTMDBTVSeason
}
/**
* get the next episode based on episodeId
@ -146,4 +127,4 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
// ?: media.playlist.first().mediaId
}
}
}

View File

@ -5,3 +5,7 @@ import android.widget.TextView
fun TextView.setDrawableTop(drawable: Int) {
this.setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0)
}
fun <T> concatenate(vararg lists: List<T>): List<T> {
return listOf(*lists).flatten()
}

View File

@ -22,116 +22,113 @@
package org.mosad.teapod.util.tmdb
import android.util.Log
import com.google.gson.Gson
import com.google.gson.JsonParser
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.Parameters
import com.github.kittinunf.fuel.json.FuelJson
import com.github.kittinunf.fuel.json.responseJson
import com.github.kittinunf.result.Result
import kotlinx.coroutines.*
import org.mosad.teapod.util.DataTypes.MediaType
import java.io.FileNotFoundException
import java.net.URL
import java.net.URLEncoder
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.mosad.teapod.util.concatenate
/**
* Controller for tmdb api integration.
* Data types are in TMDBDataTypes. For the type definitions see:
* https://developers.themoviedb.org/3/getting-started/introduction
*
* TODO evaluate Klaxon
*/
class TMDBApiController {
private val json = Json { ignoreUnknownKeys = true }
private val apiUrl = "https://api.themoviedb.org/3"
private val searchMovieUrl = "$apiUrl/search/movie"
private val searchTVUrl = "$apiUrl/search/tv"
private val detailsMovieUrl = "$apiUrl/movie"
private val detailsTVUrl = "$apiUrl/tv"
private val apiKey = "de959cf9c07a08b5ca7cb51cda9a40c2"
private val language = "de"
private val preparedParameters = "?api_key=$apiKey&language=$language"
companion object{
const val imageUrl = "https://image.tmdb.org/t/p/w500"
}
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun request(
endpoint: String,
parameters: Parameters = emptyList()
): Result<FuelJson, FuelError> = coroutineScope {
val path = "$apiUrl$endpoint"
val params = concatenate(listOf("api_key" to apiKey, "language" to language), parameters)
// TODO handle FileNotFoundException
return@coroutineScope (Dispatchers.IO) {
val (_, _, result) = Fuel.get(path, params)
.responseJson()
result
}
}
/**
* Search for a media(movie or tv show) in tmdb
* @param query The query text
* @param type The media type (movie or tv show)
* @return The media tmdb id, or -1 if not found
* @return A TMDBSearch object, or NoneTMDBSearch if nothing was found
*/
suspend fun search(query: String, type: MediaType): Int = withContext(Dispatchers.IO) {
val searchUrl = when (type) {
MediaType.MOVIE -> searchMovieUrl
MediaType.TVSHOW -> searchTVUrl
else -> {
Log.e(javaClass.name, "Wrong Type: $type")
return@withContext -1
}
}
@ExperimentalSerializationApi
suspend fun searchMulti(query: String): TMDBSearch {
val searchEndpoint = "/search/multi"
val parameters = listOf("query" to query, "include_adult" to false)
val url = URL("$searchUrl$preparedParameters&query=${URLEncoder.encode(query, "UTF-8")}")
val response = JsonParser.parseString(url.readText()).asJsonObject
val sortedResults = response.get("results").asJsonArray.toList().sortedBy {
it.asJsonObject.get("title")?.asString
}
return@withContext sortedResults.firstOrNull()?.asJsonObject?.get("id")?.asInt ?: -1
val result = request(searchEndpoint, parameters)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBSearch
}
@Suppress("BlockingMethodInNonBlockingContext")
/**
* Get details for a movie from tmdb
* @param movieId The tmdb ID of the movie
* @return A tmdb movie object, or null if not found
* @return A TMDBMovie object, or NoneTMDBMovie if not found
*/
suspend fun getMovieDetails(movieId: Int): TMDBMovie? = withContext(Dispatchers.IO) {
val url = URL("$detailsMovieUrl/$movieId?api_key=$apiKey&language=$language")
suspend fun getMovieDetails(movieId: Int): TMDBMovie {
val movieEndpoint = "/movie/$movieId"
return@withContext try {
val json = url.readText()
Gson().fromJson(json, TMDBMovie::class.java)
} catch (ex: FileNotFoundException) {
Log.w(javaClass.name, "Waring: The requested media was not found. Requested ID: $movieId", ex)
null
}
// TODO is FileNotFoundException handling needed?
val result = request(movieEndpoint)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBMovie
}
@Suppress("BlockingMethodInNonBlockingContext")
/**
* Get details for a tv show from tmdb
* @param tvId The tmdb ID of the tv show
* @return A tmdb tv show object, or null if not found
* @return A TMDBTVShow object, or NoneTMDBTVShow if not found
*/
suspend fun getTVShowDetails(tvId: Int): TMDBTVShow? = withContext(Dispatchers.IO) {
val url = URL("$detailsTVUrl/$tvId?api_key=$apiKey&language=$language")
suspend fun getTVShowDetails(tvId: Int): TMDBTVShow {
val tvShowEndpoint = "/tv/$tvId"
return@withContext try {
val json = url.readText()
Gson().fromJson(json, TMDBTVShow::class.java)
} catch (ex: FileNotFoundException) {
Log.w(javaClass.name, "Waring: The requested media was not found. Requested ID: $tvId", ex)
null
}
// TODO is FileNotFoundException handling needed?
val result = request(tvShowEndpoint)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBTVShow
}
@Suppress("BlockingMethodInNonBlockingContext")
@Suppress("unused")
/**
* Get details for a tv show season from tmdb
* @param tvId The tmdb ID of the tv show
* @param seasonNumber The tmdb season number
* @return A tmdb tv season object, or null if not found
* @return A TMDBTVSeason object, or NoneTMDBTVSeason if not found
*/
suspend fun getTVSeasonDetails(tvId: Int, seasonNumber: Int): TMDBTVSeason? = withContext(Dispatchers.IO) {
val url = URL("$detailsTVUrl/$tvId/season/$seasonNumber?api_key=$apiKey&language=$language")
suspend fun getTVSeasonDetails(tvId: Int, seasonNumber: Int): TMDBTVSeason {
val tvShowSeasonEndpoint = "/tv/$tvId/season/$seasonNumber"
return@withContext try {
val json = url.readText()
Gson().fromJson(json, TMDBTVSeason::class.java)
} catch (ex: FileNotFoundException) {
Log.w(javaClass.name, "Waring: The requested media was not found. Requested ID: $tvId, Season: $seasonNumber", ex)
null
}
// TODO is FileNotFoundException handling needed?
val result = request(tvShowSeasonEndpoint)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBTVSeason
}
}
}

View File

@ -22,71 +22,110 @@
package org.mosad.teapod.util.tmdb
import com.google.gson.annotations.SerializedName
import android.os.Build
import androidx.annotation.RequiresApi
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonNames
import java.text.DateFormat
import java.time.LocalDate
import java.util.*
/**
* New TMDB API data classes
*/
@ExperimentalSerializationApi
@Serializable
data class TMDBSearch(
val page: Int,
val results: List<TMDBSearchResult>
)
@ExperimentalSerializationApi
@Serializable
data class TMDBSearchResult(
@SerialName("id") val id: Int,
@SerialName("media_type") val mediaType: String,
@JsonNames("name", "title") val name: String, // tv show = name, movie = title
@SerialName("overview") val overview: String?,
@SerialName("poster_path") val posterPath: String?,
@SerialName("backdrop_path") val backdropPath: String?,
)
@ExperimentalSerializationApi
val NoneTMDBSearch = TMDBSearch(0, emptyList())
/**
* These data classes represent the tmdb api json objects.
* Fields which are nullable in the tmdb api are also nullable here.
*/
abstract class TMDBResult{
abstract val id: Int
abstract val name: String
abstract val overview: String? // for movies tmdb return string or null
abstract val posterPath: String?
abstract val backdropPath: String?
interface TMDBResult {
val id: Int
val name: String
val overview: String? // for movies tmdb return string or null
val posterPath: String?
val backdropPath: String?
}
data class TMDBMovie(
data class TMDBBase(
override val id: Int,
override val name: String,
override val overview: String?,
@SerializedName("poster_path")
override val posterPath: String?,
@SerializedName("backdrop_path")
override val backdropPath: String?,
@SerializedName("release_date")
val releaseDate: String,
@SerializedName("runtime")
val runtime: Int?,
// TODO generes
): TMDBResult()
override val backdropPath: String?
) : TMDBResult
@Serializable
data class TMDBMovie(
@SerialName("id") override val id: Int,
@SerialName("title") override val name: String, // for movies the name is in the field title
@SerialName("overview") override val overview: String?,
@SerialName("poster_path") override val posterPath: String?,
@SerialName("backdrop_path") override val backdropPath: String?,
@SerialName("release_date") val releaseDate: String,
@SerialName("runtime") val runtime: Int?,
@SerialName("status") val status: String,
// TODO generes
) : TMDBResult
@Serializable
data class TMDBTVShow(
override val id: Int,
override val name: String,
override val overview: String,
@SerializedName("poster_path")
override val posterPath: String?,
@SerializedName("backdrop_path")
override val backdropPath: String?,
@SerializedName("first_air_date")
val firstAirDate: String,
@SerializedName("status")
val status: String,
@SerialName("id")override val id: Int,
@SerialName("name")override val name: String,
@SerialName("overview")override val overview: String,
@SerialName("poster_path") override val posterPath: String?,
@SerialName("backdrop_path") override val backdropPath: String?,
@SerialName("first_air_date") val firstAirDate: String,
@SerialName("last_air_date") val lastAirDate: String,
@SerialName("status") val status: String,
// TODO generes
): TMDBResult()
) : TMDBResult
// use null for nullable types, the gui needs to handle/implement a fallback for null values
val NoneTMDB = TMDBBase(0, "", "", null, null)
val NoneTMDBMovie = TMDBMovie(0, "", "", null, null, "", null, "")
val NoneTMDBTVShow = TMDBTVShow(0, "", "", null, null, "", "", "")
@Serializable
data class TMDBTVSeason(
val id: Int,
val name: String,
val overview: String,
@SerializedName("poster_path")
val posterPath: String?,
@SerializedName("air_date")
val airDate: String,
@SerializedName("episodes")
val episodes: List<TMDBTVEpisode>,
@SerializedName("season_number")
val seasonNumber: Int
@SerialName("id") val id: Int,
@SerialName("name") val name: String,
@SerialName("overview") val overview: String,
@SerialName("poster_path") val posterPath: String?,
@SerialName("air_date") val airDate: String,
@SerialName("episodes") val episodes: List<TMDBTVEpisode>,
@SerialName("season_number") val seasonNumber: Int
)
@Serializable
data class TMDBTVEpisode(
val id: Int,
val name: String,
val overview: String,
@SerializedName("air_date")
val airDate: String,
@SerializedName("episode_number")
val episodeNumber: Int
)
@SerialName("id") val id: Int,
@SerialName("name") val name: String,
@SerialName("overview") val overview: String,
@SerialName("air_date") val airDate: String,
@SerialName("episode_number") val episodeNumber: Int
)
// use null for nullable types, the gui needs to handle/implement a fallback for null values
val NoneTMDBTVSeason = TMDBTVSeason(0, "", "", null, "", emptyList(), 0)