mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 04:32:43 -05:00
episode details screen
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.*
|
||||
|
||||
@@ -89,4 +88,26 @@ interface TvApi {
|
||||
|
||||
@GET("tv/{id}/season/{season}/watch/providers")
|
||||
suspend fun getSeasonWatchProviders(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<WatchProviderResponse>
|
||||
|
||||
@GET("tv/{id}/season/{season}/episode/{episode}")
|
||||
suspend fun getEpisodeDetails(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int, @Path("episode") episodeNumber: Int): Response<Episode>
|
||||
|
||||
@GET("tv/{id}/season/{season}/episode/{episode}/account_states")
|
||||
suspend fun getEpisodeAccountStates(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int, @Path("episode") episodeNumber: Int): Response<EpisodeAccountState>
|
||||
|
||||
@GET("tv/{id}/season/{season}/episode/{episode}/credits")
|
||||
suspend fun getEpisodeCredits(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int, @Path("episode") episodeNumber: Int): Response<EpisodeCastAndCrew>
|
||||
|
||||
@GET("tv/{id}/season/{season}/episode/{episode}/images")
|
||||
suspend fun getEpisodeImages(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int, @Path("episode") episodeNumber: Int): Response<EpisodeImageCollection>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("tv/{id}/season/{season}/episode/{episode}/rating")
|
||||
suspend fun postTvEpisodeRatingAsUser(
|
||||
@Path("id") id: Int,
|
||||
@Path("season") seasonNumber: Int,
|
||||
@Path("episode") episodeNumber: Int,
|
||||
@Query("session_id") sessionId: String,
|
||||
@Field("value") rating: Float
|
||||
): Response<StatusResponse>
|
||||
}
|
||||
@@ -10,6 +10,11 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CrewMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedTv
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Episode
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeAccountState
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeCastMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeCrewMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||
@@ -25,6 +30,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCrewMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
|
||||
import com.owenlejeune.tvtime.extensions.createEpisodeKey
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -84,6 +90,30 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
val seasonWatchProviders: MutableMap<Int, out Map<Int, WatchProviders>>
|
||||
get() = _seasonWatchProviders
|
||||
|
||||
private val _episodesMap = Collections.synchronizedMap(mutableStateMapOf<String, Episode>())
|
||||
val episodesMap: Map<String, Episode>
|
||||
get() = _episodesMap
|
||||
|
||||
private val _episodeAccountStates = Collections.synchronizedMap(mutableStateMapOf<String, EpisodeAccountState>())
|
||||
val episodeAccountStates: Map<String, EpisodeAccountState>
|
||||
get() = _episodeAccountStates
|
||||
|
||||
private val _episodeCast = Collections.synchronizedMap(mutableStateMapOf<String, List<EpisodeCastMember>>())
|
||||
val episodeCast: Map<String, List<EpisodeCastMember>>
|
||||
get() = _episodeCast
|
||||
|
||||
private val _episodeCrew = Collections.synchronizedMap(mutableStateMapOf<String, List<EpisodeCrewMember>>())
|
||||
val episodeCrew: Map<String, List<EpisodeCrewMember>>
|
||||
get() = _episodeCrew
|
||||
|
||||
private val _episodeGuestStars = Collections.synchronizedMap(mutableStateMapOf<String, List<EpisodeCastMember>>())
|
||||
val episodeGuestStars: Map<String, List<EpisodeCastMember>>
|
||||
get() = _episodeGuestStars
|
||||
|
||||
private val _episodeImages = Collections.synchronizedMap(mutableStateMapOf<String, EpisodeImageCollection>())
|
||||
val episodeImages: Map<String, EpisodeImageCollection>
|
||||
get() = _episodeImages
|
||||
|
||||
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val imagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val castCrewLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
@@ -100,6 +130,10 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
val seasonImagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val seasonVideosLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val seasonWatchProvidersLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val episodeLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val episodeAccountStateLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val episodeCreditsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val episodeImagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
|
||||
val isPopularTvLoading = mutableStateOf(false)
|
||||
val isTopRatedTvLoading = mutableStateOf(false)
|
||||
@@ -284,8 +318,8 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
suspend fun getSeasonWatchProviders(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getSeasonWatchProviders(seriesId, seasonId) },
|
||||
{ si ->
|
||||
si.results[Locale.getDefault().country]?.let { wp ->
|
||||
{ swp ->
|
||||
swp.results[Locale.getDefault().country]?.let { wp ->
|
||||
_seasonWatchProviders
|
||||
.getOrPut(seriesId) {
|
||||
emptyMap<Int, WatchProviders>().toMutableMap()
|
||||
@@ -297,6 +331,91 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getEpisode(
|
||||
seriesId: Int,
|
||||
seasonId: Int,
|
||||
episodeId: Int,
|
||||
refreshing: Boolean
|
||||
) {
|
||||
loadRemoteData(
|
||||
{ service.getEpisodeDetails(seriesId, seasonId, episodeId) },
|
||||
{ episode ->
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
_episodesMap[key] = episode
|
||||
},
|
||||
episodeLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getEpisodeAccountStates(
|
||||
seriesId: Int,
|
||||
seasonId: Int,
|
||||
episodeId: Int,
|
||||
refreshing: Boolean
|
||||
) {
|
||||
loadRemoteData(
|
||||
{ service.getEpisodeAccountStates(seriesId, seasonId, episodeId) },
|
||||
{ eas ->
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
_episodeAccountStates[key] = eas
|
||||
},
|
||||
episodeAccountStateLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getEpisodeCredits(
|
||||
seriesId: Int,
|
||||
seasonId: Int,
|
||||
episodeId: Int,
|
||||
refreshing: Boolean
|
||||
) {
|
||||
loadRemoteData(
|
||||
{ service.getEpisodeCredits(seriesId, seasonId, episodeId) },
|
||||
{ ec ->
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
_episodeCast[key] = ec.cast
|
||||
_episodeCrew[key] = ec.crew
|
||||
_episodeGuestStars[key] = ec.guestStars
|
||||
},
|
||||
episodeCreditsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getEpisodeImages(
|
||||
seriesId: Int,
|
||||
seasonId: Int,
|
||||
episodeId: Int,
|
||||
refreshing: Boolean
|
||||
) {
|
||||
loadRemoteData(
|
||||
{ service.getEpisodeImages(seriesId, seasonId, episodeId) },
|
||||
{ ei ->
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
_episodeImages[key] = ei
|
||||
},
|
||||
episodeImagesLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun postEpisodeRating(
|
||||
seriesId: Int,
|
||||
seasonId: Int,
|
||||
episodeId: Int,
|
||||
rating: Float
|
||||
) {
|
||||
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
||||
val response = service.postTvEpisodeRatingAsUser(seriesId, seasonId, episodeId, session.sessionId, rating)
|
||||
if (response.isSuccessful) {
|
||||
Log.d(TAG, "Successfully posted rating")
|
||||
} else {
|
||||
Log.w(TAG, "Issue posting rating")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun postRating(id: Int, rating: Float) {
|
||||
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
||||
val response = service.postTvRatingAsUser(id, session.sessionId, rating)
|
||||
|
||||
@@ -2,19 +2,19 @@ package com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.BaseDeserializer
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SeasonAccountStatesResult
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeAccountState
|
||||
|
||||
class SeasonAccountStatesResultDeserializer: BaseDeserializer<SeasonAccountStatesResult>() {
|
||||
class SeasonAccountStatesResultDeserializer: BaseDeserializer<EpisodeAccountState>() {
|
||||
|
||||
override fun processJson(obj: JsonObject): SeasonAccountStatesResult {
|
||||
override fun processJson(obj: JsonObject): EpisodeAccountState {
|
||||
val id = obj.get("id").asInt
|
||||
val episodeNumber = obj.get("episode_number").asInt
|
||||
val episodeNumber = obj.get("episode_number")?.asInt
|
||||
return try {
|
||||
val isRated = obj.get("rated").asBoolean
|
||||
SeasonAccountStatesResult(id, episodeNumber, isRated, -1)
|
||||
EpisodeAccountState(id, episodeNumber, isRated, -1)
|
||||
} catch (e: Exception) {
|
||||
val rating = obj.get("rated").asJsonObject.get("value").asInt
|
||||
SeasonAccountStatesResult(id, episodeNumber, true, rating)
|
||||
EpisodeAccountState(id, episodeNumber, true, rating)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,4 +9,10 @@ abstract class CastAndCrew<C, R>(
|
||||
|
||||
class TvCastAndCrew(cast: List<TvCastMember>, crew: List<TvCrewMember>): CastAndCrew<TvCastMember, TvCrewMember>(cast, crew)
|
||||
|
||||
class MovieCastAndCrew(cast: List<MovieCastMember>, crew: List<MovieCrewMember>): CastAndCrew<MovieCastMember, MovieCrewMember>(cast, crew)
|
||||
class MovieCastAndCrew(cast: List<MovieCastMember>, crew: List<MovieCrewMember>): CastAndCrew<MovieCastMember, MovieCrewMember>(cast, crew)
|
||||
|
||||
class EpisodeCastAndCrew(
|
||||
cast: List<EpisodeCastMember>,
|
||||
crew: List<EpisodeCrewMember>,
|
||||
@SerializedName("guest_stars") val guestStars: List<EpisodeCastMember>
|
||||
): CastAndCrew<EpisodeCastMember, EpisodeCrewMember>(cast, crew)
|
||||
@@ -36,7 +36,8 @@ class EpisodeCastMember(
|
||||
originalName: String,
|
||||
popularity: Float,
|
||||
order: Int,
|
||||
@SerializedName("credit_id") val creditId: String
|
||||
@SerializedName("credit_id") val creditId: String,
|
||||
@SerializedName("character") val character: String
|
||||
): CastMember(id, name, gender, profilePath, isAdult, knownForDepartment, originalName, popularity, order)
|
||||
|
||||
class TvCastMember(
|
||||
@@ -90,7 +91,8 @@ class EpisodeCrewMember(
|
||||
originalName: String,
|
||||
popularity: Float,
|
||||
department: String,
|
||||
@SerializedName("credit_id") val creditId: String
|
||||
@SerializedName("credit_id") val creditId: String,
|
||||
@SerializedName("job") val job: String
|
||||
): CrewMember(id, name, gender, profilePath, isAdult, knownForDepartment, originalName, popularity, department)
|
||||
|
||||
class TvCrewMember(
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class EpisodeImageCollection(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("stills") val stills: List<EpisodeStill>
|
||||
)
|
||||
|
||||
data class EpisodeStill(
|
||||
@SerializedName("aspect_ratio") val aspectRation: Float,
|
||||
@SerializedName("height") val height: Int,
|
||||
@SerializedName("iso_639_1") val language: String?,
|
||||
@SerializedName("file_path") val stillPath: String,
|
||||
@SerializedName("vote_average") val voteAverage: Float,
|
||||
@SerializedName("vote_count") val voteCount: Int,
|
||||
@SerializedName("width") val width: Int
|
||||
)
|
||||
@@ -5,7 +5,7 @@ import com.owenlejeune.tvtime.utils.types.Gender
|
||||
|
||||
open class Person(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("name", alternate = ["title"]) val name: String,
|
||||
@SerializedName("gender") val gender: Gender,
|
||||
@SerializedName("profile_path") val profilePath: String?
|
||||
)
|
||||
|
||||
@@ -4,12 +4,12 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
class SeasonAccountStates(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("results") val results: List<SeasonAccountStatesResult>
|
||||
@SerializedName("results") val results: List<EpisodeAccountState>
|
||||
)
|
||||
|
||||
class SeasonAccountStatesResult(
|
||||
class EpisodeAccountState(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("episode_number") val episodeNumber: Int,
|
||||
@SerializedName("episode_number") val episodeNumber: Int?,
|
||||
val isRated: Boolean,
|
||||
var rating: Int
|
||||
)
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.owenlejeune.tvtime.di.modules
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.owenlejeune.tvtime.BuildConfig
|
||||
import com.owenlejeune.tvtime.api.*
|
||||
import com.owenlejeune.tvtime.api.nextmcu.NextMCUClient
|
||||
@@ -28,7 +26,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CreditMedia
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCast
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SeasonAccountStatesResult
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeAccountState
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
|
||||
@@ -37,7 +35,6 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.SettingsViewModel
|
||||
import com.owenlejeune.tvtime.utils.NetworkConnectivityService
|
||||
import com.owenlejeune.tvtime.utils.NetworkConnectivityServiceImpl
|
||||
import com.owenlejeune.tvtime.utils.ResourceUtils
|
||||
@@ -89,7 +86,7 @@ val networkModule = module {
|
||||
DetailCrew::class.java to DetailCrewDeserializer(),
|
||||
CreditMedia::class.java to CreditMediaDeserializer(),
|
||||
Date::class.java to DateTypeAdapter(),
|
||||
SeasonAccountStatesResult::class.java to SeasonAccountStatesResultDeserializer()
|
||||
EpisodeAccountState::class.java to SeasonAccountStatesResultDeserializer()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,4 +12,7 @@ fun <T> anyOf(vararg items: T, predicate: (T) -> Boolean): Boolean = items.any(p
|
||||
|
||||
fun <T: Any> T.isIn(vararg items: T): Boolean = items.any { it == this }
|
||||
|
||||
fun <T> pairOf(a: T, b: T) = Pair(a, b)
|
||||
fun <T> pairOf(a: T, b: T) = Pair(a, b)
|
||||
|
||||
fun createEpisodeKey(seriesId: Int, seasonNumber: Int, episodeNumber: Int): String
|
||||
= listOf(seriesId, seasonNumber, episodeNumber).joinToString("_")
|
||||
@@ -54,6 +54,8 @@ import com.google.accompanist.pager.rememberPagerState
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.LoadingState
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Episode
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeCastMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.EpisodeCrewMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Image
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCastMember
|
||||
@@ -64,6 +66,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCrewMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderDetails
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
|
||||
import com.owenlejeune.tvtime.extensions.combineWith
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||
import com.owenlejeune.tvtime.extensions.toDp
|
||||
@@ -147,13 +150,15 @@ fun DetailHeader(
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
PosterItem(
|
||||
url = posterUrl,
|
||||
title = posterContentDescription,
|
||||
elevation = elevation,
|
||||
overrideShowTitle = false,
|
||||
enabled = false
|
||||
)
|
||||
posterUrl?.let {
|
||||
PosterItem(
|
||||
url = posterUrl,
|
||||
title = posterContentDescription,
|
||||
elevation = elevation,
|
||||
overrideShowTitle = false,
|
||||
enabled = false
|
||||
)
|
||||
}
|
||||
|
||||
rating?.let {
|
||||
if (it > 0f) {
|
||||
@@ -418,7 +423,9 @@ fun AdditionalDetailItem(
|
||||
|
||||
@Composable
|
||||
fun EpisodeItem(
|
||||
seriesId: Int,
|
||||
episode: Episode,
|
||||
appNavController: NavController,
|
||||
elevation: Dp = 10.dp,
|
||||
maxDescriptionLines: Int = 2,
|
||||
rating: Int? = null
|
||||
@@ -430,7 +437,10 @@ fun EpisodeItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
|
||||
val codedId = seriesId.combineWith(episode.seasonNumber).combineWith(episode.episodeNumber)
|
||||
appNavController.navigate(
|
||||
AppNavItem.DetailView.withArgs(MediaViewType.EPISODE, codedId)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Box {
|
||||
@@ -527,6 +537,12 @@ fun CastCrewCard(
|
||||
val epsCount = person.totalEpisodeCount
|
||||
"$roles ($epsCount Eps.)"
|
||||
}
|
||||
is EpisodeCastMember -> {
|
||||
person.character
|
||||
}
|
||||
is EpisodeCrewMember -> {
|
||||
person.job
|
||||
}
|
||||
else -> null
|
||||
},
|
||||
imageUrl = TmdbUtils.getFullPersonImagePath(person),
|
||||
@@ -584,7 +600,8 @@ fun WatchProvidersCard(
|
||||
|
||||
Crossfade(
|
||||
modifier = modifier.padding(top = 4.dp, bottom = 12.dp),
|
||||
targetState = selected.value
|
||||
targetState = selected.value,
|
||||
label = ""
|
||||
) { value ->
|
||||
WatchProviderContainer(watchProviders = value!!, link = providers.link)
|
||||
}
|
||||
@@ -757,4 +774,66 @@ fun ImagesCard(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CastCard(
|
||||
title: String,
|
||||
isLoading: Boolean,
|
||||
cast: List<Person>?,
|
||||
appNavController: NavController,
|
||||
onSeeMore: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.primary,
|
||||
textColor: Color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
ContentCard(
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
backgroundColor = backgroundColor,
|
||||
textColor = textColor
|
||||
) {
|
||||
Column {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
item { Spacer(modifier = Modifier.width(8.dp)) }
|
||||
if (isLoading) {
|
||||
items(5) {
|
||||
PlaceholderPosterItem()
|
||||
}
|
||||
} else {
|
||||
items(cast?.size ?: 0) { i ->
|
||||
cast?.get(i)?.let {
|
||||
CastCrewCard(appNavController = appNavController, person = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
item { Spacer(modifier = Modifier.width(8.dp)) }
|
||||
}
|
||||
|
||||
onSeeMore?.let {
|
||||
if (isLoading) {
|
||||
Text(
|
||||
text = "",
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, bottom = 12.dp)
|
||||
.width(80.dp)
|
||||
.shimmerBackground(RoundedCornerShape(10.dp))
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(R.string.see_all_cast_and_crew),
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.inversePrimary,
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, bottom = 12.dp)
|
||||
.clickable(onClick = onSeeMore)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.screens.AboutScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.AccountScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.CastCrewListScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.EpisodeDetailsScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.GalleryView
|
||||
import com.owenlejeune.tvtime.ui.screens.HomeScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.KeywordResultsScreen
|
||||
@@ -129,6 +130,12 @@ fun AppNavigationHost(
|
||||
codedId = id
|
||||
)
|
||||
}
|
||||
MediaViewType.EPISODE -> {
|
||||
EpisodeDetailsScreen(
|
||||
appNavController = appNavController,
|
||||
codedId = id
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
appNavController.popBackStack()
|
||||
Toast.makeText(LocalContext.current, stringResource(R.string.unexpected_error), Toast.LENGTH_SHORT).show()
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Episode
|
||||
import com.owenlejeune.tvtime.extensions.createEpisodeKey
|
||||
import com.owenlejeune.tvtime.extensions.toCompositeParts
|
||||
import com.owenlejeune.tvtime.ui.components.BackButton
|
||||
import com.owenlejeune.tvtime.ui.components.CastCard
|
||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
||||
import com.owenlejeune.tvtime.ui.theme.Typography
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private fun fetchData(
|
||||
mainViewModel: MainViewModel,
|
||||
seriesId: Int,
|
||||
seasonNumber: Int,
|
||||
episodeNumber: Int,
|
||||
force: Boolean = false
|
||||
) {
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
scope.launch { mainViewModel.getEpisode(seriesId, seasonNumber, episodeNumber, force) }
|
||||
scope.launch { mainViewModel.getEpisodeCredits(seriesId, seasonNumber, episodeNumber, force) }
|
||||
scope.launch { mainViewModel.getEpisodeImages(seriesId, seasonNumber, episodeNumber, force) }
|
||||
if (SessionManager.isLoggedIn) {
|
||||
scope.launch { mainViewModel.getEpisodeAccountStates(seriesId, seasonNumber, episodeNumber, force) }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EpisodeDetailsScreen(
|
||||
appNavController: NavController,
|
||||
codedId: Int
|
||||
) {
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
val applicationViewModel = viewModel<ApplicationViewModel>()
|
||||
|
||||
applicationViewModel.statusBarColor.value = MaterialTheme.colorScheme.background
|
||||
applicationViewModel.navigationBarColor.value = MaterialTheme.colorScheme.background
|
||||
|
||||
val (a, b) = codedId.toCompositeParts()
|
||||
val episodeNumber = minOf(a, b)
|
||||
val (c, d) = maxOf(a, b).toCompositeParts()
|
||||
val seasonNumber = minOf(c, d)
|
||||
val seriesId = maxOf(c, d)
|
||||
LaunchedEffect(Unit) {
|
||||
fetchData(mainViewModel, seriesId, seasonNumber, episodeNumber)
|
||||
}
|
||||
|
||||
val episodeKey = createEpisodeKey(seriesId, seasonNumber, episodeNumber)
|
||||
|
||||
val episodesMap = remember { mainViewModel.tvEpisodes }
|
||||
val episode = episodesMap[episodeKey]
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TVTTopAppBar(
|
||||
title = { },
|
||||
appNavController = appNavController,
|
||||
navigationIcon = { BackButton(navController = appNavController) }
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
episode?.let {
|
||||
EpisodeContent(
|
||||
seriesId = seriesId,
|
||||
episodeKey = episodeKey,
|
||||
episode = episode,
|
||||
appNavController = appNavController,
|
||||
mainViewModel = mainViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
private fun EpisodeContent(
|
||||
seriesId: Int,
|
||||
episodeKey: String,
|
||||
episode: Episode,
|
||||
appNavController: NavController,
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.background)
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
DetailHeader(
|
||||
backdropUrl = TmdbUtils.getFullEpisodeStillPath(episode.stillPath)
|
||||
)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = episode.name,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = Typography.headlineLarge,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
TmdbUtils.convertEpisodeDate(episode.airDate)?.let {
|
||||
Text(
|
||||
text = it,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
|
||||
val castMap = remember { mainViewModel.tvEpisodeCast }
|
||||
val cast = castMap[episodeKey]
|
||||
cast?.let {
|
||||
CastCard(
|
||||
title = stringResource(R.string.cast_label),
|
||||
isLoading = false,
|
||||
cast = cast,
|
||||
appNavController = appNavController
|
||||
)
|
||||
}
|
||||
|
||||
val guestStarsMap = remember { mainViewModel.tvEpisodeGuestStars }
|
||||
val guestStars = guestStarsMap[episodeKey]
|
||||
guestStars?.let {
|
||||
CastCard(
|
||||
title = stringResource(id = R.string.guest_stars_label),
|
||||
isLoading = false,
|
||||
cast = guestStars,
|
||||
appNavController = appNavController
|
||||
)
|
||||
}
|
||||
|
||||
val crewMap = remember { mainViewModel.tvEpisodeCrew }
|
||||
val crew = crewMap[episodeKey]
|
||||
crew?.let {
|
||||
CastCard(
|
||||
title = stringResource(id = R.string.crew_label),
|
||||
isLoading = false,
|
||||
cast = crew,
|
||||
appNavController = appNavController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,6 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedTv
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Genre
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||
import com.owenlejeune.tvtime.extensions.DateFormat
|
||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||
import com.owenlejeune.tvtime.extensions.combineWith
|
||||
@@ -85,13 +84,13 @@ import com.owenlejeune.tvtime.extensions.format
|
||||
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
||||
import com.owenlejeune.tvtime.extensions.isIn
|
||||
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.ActionsView
|
||||
import com.owenlejeune.tvtime.ui.components.AdditionalDetailItem
|
||||
import com.owenlejeune.tvtime.ui.components.AvatarImage
|
||||
import com.owenlejeune.tvtime.ui.components.BackButton
|
||||
import com.owenlejeune.tvtime.ui.components.CastCard
|
||||
import com.owenlejeune.tvtime.ui.components.CastCrewCard
|
||||
import com.owenlejeune.tvtime.ui.components.ChipDefaults
|
||||
import com.owenlejeune.tvtime.ui.components.ChipGroup
|
||||
@@ -100,14 +99,11 @@ import com.owenlejeune.tvtime.ui.components.ChipStyle
|
||||
import com.owenlejeune.tvtime.ui.components.CircleBackgroundColorImage
|
||||
import com.owenlejeune.tvtime.ui.components.ContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.ExternalIdsArea
|
||||
import com.owenlejeune.tvtime.ui.components.FullScreenThumbnailVideoPlayer
|
||||
import com.owenlejeune.tvtime.ui.components.HtmlText
|
||||
import com.owenlejeune.tvtime.ui.components.ImageGalleryOverlay
|
||||
import com.owenlejeune.tvtime.ui.components.ListContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.PlaceholderDetailHeader
|
||||
import com.owenlejeune.tvtime.ui.components.PlaceholderPosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.RoundedChip
|
||||
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
||||
@@ -192,9 +188,6 @@ fun MediaDetailScreen(
|
||||
if (type == MediaViewType.TV) {
|
||||
LaunchedEffect(mediaItem) {
|
||||
val lastSeason = (mediaItem as DetailedTv?)?.numberOfSeasons ?: 0
|
||||
// for (i in lastSeason downTo 0) {
|
||||
// mainViewModel.getSeason(itemId, i)
|
||||
// }
|
||||
if (lastSeason > 0) {
|
||||
mainViewModel.getSeason(itemId, lastSeason)
|
||||
}
|
||||
@@ -351,7 +344,7 @@ fun MediaViewContent(
|
||||
appNavController = appNavController
|
||||
)
|
||||
|
||||
CastCard(
|
||||
CastArea(
|
||||
itemId = itemId,
|
||||
appNavController = appNavController,
|
||||
type = type,
|
||||
@@ -795,7 +788,7 @@ private fun AdditionalTvItems(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CastCard(
|
||||
private fun CastArea(
|
||||
itemId: Int,
|
||||
type: MediaViewType,
|
||||
mainViewModel: MainViewModel,
|
||||
@@ -808,65 +801,21 @@ private fun CastCard(
|
||||
val loadingState = remember { mainViewModel.produceDetailsLoadingStateFor(type) }
|
||||
val isLoading = loadingState.value.isIn(LoadingState.LOADING, LoadingState.REFRESHING)
|
||||
|
||||
ContentCard(
|
||||
CastCard(
|
||||
modifier = modifier,
|
||||
title = stringResource(R.string.cast_label),
|
||||
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||
textColor = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Column {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
if (isLoading) {
|
||||
items(5) {
|
||||
PlaceholderPosterItem()
|
||||
}
|
||||
} else {
|
||||
items(cast?.size ?: 0) { i ->
|
||||
cast?.get(i)?.let {
|
||||
CastCrewCard(appNavController = appNavController, person = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
Text(
|
||||
text = "",
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, bottom = 12.dp)
|
||||
.width(80.dp)
|
||||
.shimmerBackground(RoundedCornerShape(10.dp))
|
||||
isLoading = isLoading,
|
||||
cast = cast,
|
||||
appNavController = appNavController,
|
||||
onSeeMore = {
|
||||
appNavController.navigate(
|
||||
AppNavItem.CastCrewListView.withArgs(
|
||||
type,
|
||||
itemId
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(R.string.see_all_cast_and_crew),
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.inversePrimary,
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, bottom = 12.dp)
|
||||
.clickable {
|
||||
appNavController.navigate(
|
||||
AppNavItem.CastCrewListView.withArgs(
|
||||
type,
|
||||
itemId
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -193,6 +193,7 @@ private fun SeasonContent(
|
||||
) {
|
||||
season.episodes.forEach { episode ->
|
||||
DrawEpisodeCard(
|
||||
seriesId = seriesId,
|
||||
episode = episode,
|
||||
accountStates = accountStates,
|
||||
appNavController = appNavController
|
||||
@@ -228,23 +229,32 @@ private fun SeasonContent(
|
||||
|
||||
@Composable
|
||||
private fun DrawEpisodeCard(
|
||||
seriesId: Int,
|
||||
episode: Episode,
|
||||
accountStates: SeasonAccountStates?,
|
||||
appNavController: NavController
|
||||
) {
|
||||
val rating = accountStates?.results?.find { it.id == episode.id }?.takeUnless { !it.isRated }?.rating
|
||||
SeasonEpisodeItem(appNavController = appNavController, episode = episode, rating = rating)
|
||||
SeasonEpisodeItem(
|
||||
appNavController = appNavController,
|
||||
seriesId = seriesId,
|
||||
episode = episode,
|
||||
rating = rating
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeasonEpisodeItem(
|
||||
appNavController: NavController,
|
||||
seriesId: Int,
|
||||
episode: Episode,
|
||||
rating: Int?
|
||||
) {
|
||||
ContentCard {
|
||||
EpisodeItem(
|
||||
seriesId = seriesId,
|
||||
episode = episode,
|
||||
appNavController = appNavController,
|
||||
elevation = 0.dp,
|
||||
maxDescriptionLines = 5,
|
||||
rating = rating
|
||||
|
||||
@@ -174,7 +174,11 @@ private fun SeasonSection(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
season.episodes.forEach { episode ->
|
||||
EpisodeItem(episode = episode)
|
||||
EpisodeItem(
|
||||
seriesId = seriesId,
|
||||
episode = episode,
|
||||
appNavController = appNavController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CrewMember
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Episode
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Keyword
|
||||
@@ -24,6 +25,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMedia
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
|
||||
import com.owenlejeune.tvtime.extensions.createEpisodeKey
|
||||
import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabNavItem
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||
@@ -119,6 +121,12 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val tvSeasonImages = tvService.seasonImages
|
||||
val tvSeasonVideos = tvService.seasonVideos
|
||||
val tvSeasonWatchProviders = tvService.seasonWatchProviders
|
||||
val tvEpisodes = tvService.episodesMap
|
||||
val tvEpisodesAccountStates = tvService.episodeAccountStates
|
||||
val tvEpisodeCast = tvService.episodeCast
|
||||
val tvEpisodeCrew = tvService.episodeCrew
|
||||
val tvEpisodeGuestStars = tvService.episodeGuestStars
|
||||
val tvEpisodeImages = tvService.episodeImages
|
||||
|
||||
val tvDetailsLoadingState = tvService.detailsLoadingState
|
||||
val tvImagesLoadingState = tvService.imagesLoadingState
|
||||
@@ -136,6 +144,10 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val tvSeasonImagesLoadingState = tvService.seasonImagesLoadingState
|
||||
val tvSeasonVideosLoadingState = tvService.seasonVideosLoadingState
|
||||
val tvSeasonWatchProvidersLoadingState = tvService.seasonWatchProvidersLoadingState
|
||||
val tvEpisodeLoadingState = tvService.episodeLoadingState
|
||||
val tvEpisodeAccountStateLoadingState = tvService.episodeAccountStateLoadingState
|
||||
val tvEpisodeCreditsLoadingState = tvService.episodeCreditsLoadingState
|
||||
val tvEpisodeImagesLoadingState = tvService.episodeImagesLoadingState
|
||||
|
||||
val isPopularTvLoading = tvService.isPopularTvLoading
|
||||
val isTopRatedTvLoading = tvService.isTopRatedTvLoading
|
||||
@@ -559,6 +571,34 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getEpisode(seriesId: Int, seasonId: Int, episodeId: Int, force: Boolean = false) {
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
if (tvEpisodes[key] == null || force) {
|
||||
tvService.getEpisode(seriesId, seasonId, episodeId, force)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getEpisodeAccountStates(seriesId: Int, seasonId: Int, episodeId: Int, force: Boolean = false) {
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
if (tvEpisodesAccountStates[key] == null || force) {
|
||||
tvService.getEpisodeAccountStates(seriesId, seasonId, episodeId, force)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getEpisodeCredits(seriesId: Int, seasonId: Int, episodeInt: Int, force: Boolean = false) {
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeInt)
|
||||
if (tvEpisodeCast[key] == null || tvEpisodeCrew[key] == null || tvEpisodeGuestStars[key] == null || force) {
|
||||
tvService.getEpisodeCredits(seriesId, seasonId, episodeInt, force)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getEpisodeImages(seriesId: Int, seasonId: Int, episodeId: Int, force: Boolean = false) {
|
||||
val key = createEpisodeKey(seriesId, seasonId, episodeId)
|
||||
if (tvEpisodeImages[key] == null || force) {
|
||||
tvService.getEpisodeImages(seriesId, seasonId, episodeId, force)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ComposableNaming")
|
||||
@Composable
|
||||
fun monitorDetailsLoadingRefreshing(refreshing: MutableState<Boolean>) {
|
||||
|
||||
@@ -6,5 +6,6 @@ enum class Gender(val rawValue: Int) {
|
||||
@SerializedName("1")
|
||||
MALE(1),
|
||||
@SerializedName("2")
|
||||
FEMALE(2)
|
||||
FEMALE(2),
|
||||
UNDEFINED(-1)
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
<!-- Headings -->
|
||||
<string name="cast_label">Cast</string>
|
||||
<string name="crew_label">Crew</string>
|
||||
<string name="recommended_label">Recommended</string>
|
||||
<string name="videos_label">Videos</string>
|
||||
<string name="known_for_label">Known For</string>
|
||||
|
||||
Reference in New Issue
Block a user