mirror of
https://github.com/owenlejeune/TVTime.git
synced 2026-01-06 23:01:18 -05:00
tv series season details screen
This commit is contained in:
@@ -10,7 +10,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImage
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Image
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
|
||||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||||
@@ -27,7 +27,7 @@ class PeopleService: KoinComponent {
|
|||||||
val peopleMap = Collections.synchronizedMap(mutableStateMapOf<Int, DetailPerson>())
|
val peopleMap = Collections.synchronizedMap(mutableStateMapOf<Int, DetailPerson>())
|
||||||
val castMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<DetailCast>>())
|
val castMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<DetailCast>>())
|
||||||
val crewMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<DetailCrew>>())
|
val crewMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<DetailCrew>>())
|
||||||
val imagesMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<PersonImage>>())
|
val imagesMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<Image>>())
|
||||||
val externalIdsMap = Collections.synchronizedMap(mutableStateMapOf<Int, ExternalIds>())
|
val externalIdsMap = Collections.synchronizedMap(mutableStateMapOf<Int, ExternalIds>())
|
||||||
|
|
||||||
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
|||||||
@@ -73,4 +73,19 @@ interface TvApi {
|
|||||||
|
|
||||||
@GET("tv/{id}/season/{season}")
|
@GET("tv/{id}/season/{season}")
|
||||||
suspend fun getSeason(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<Season>
|
suspend fun getSeason(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<Season>
|
||||||
|
|
||||||
|
@GET("tv/{id}/season/{season}/account_states")
|
||||||
|
suspend fun getSeasonAccountStates(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<SeasonAccountStates>
|
||||||
|
|
||||||
|
@GET("tv/{id}/season/{season}/aggregate_credits")
|
||||||
|
suspend fun getSeasonCredits(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<TvCastAndCrew>
|
||||||
|
|
||||||
|
@GET("tv/{id}/season/{season}/images")
|
||||||
|
suspend fun getSeasonImages(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<ImageCollection>
|
||||||
|
|
||||||
|
@GET("tv/{id}/season/{season}/videos")
|
||||||
|
suspend fun getSeasonVideos(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<VideoResponse>
|
||||||
|
|
||||||
|
@GET("tv/{id}/season/{season}/watch/providers")
|
||||||
|
suspend fun getSeasonWatchProviders(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<WatchProviderResponse>
|
||||||
}
|
}
|
||||||
@@ -27,9 +27,13 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMedia
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMedia
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SeasonAccountStates
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCastAndCrew
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCastMember
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
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.Video
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse
|
||||||
@@ -69,6 +73,30 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
val seasons: MutableMap<Int, out Set<Season>>
|
val seasons: MutableMap<Int, out Set<Season>>
|
||||||
get() = _seasons
|
get() = _seasons
|
||||||
|
|
||||||
|
private val _seasonAccountStates = Collections.synchronizedMap(mutableStateMapOf<Int, MutableMap<Int, SeasonAccountStates>>())
|
||||||
|
val seasonAccountStates: MutableMap<Int, out Map<Int, SeasonAccountStates>>
|
||||||
|
get() = _seasonAccountStates
|
||||||
|
|
||||||
|
private val _seasonCast = Collections.synchronizedMap(mutableStateMapOf<Int, MutableMap<Int, List<TvCastMember>>>())
|
||||||
|
val seasonCast: MutableMap<Int, out Map<Int, List<TvCastMember>>>
|
||||||
|
get() = _seasonCast
|
||||||
|
|
||||||
|
private val _seasonCrew = Collections.synchronizedMap(mutableStateMapOf<Int, MutableMap<Int, List<TvCrewMember>>>())
|
||||||
|
val seasonCrew: MutableMap<Int, out Map<Int, List<TvCrewMember>>>
|
||||||
|
get() = _seasonCrew
|
||||||
|
|
||||||
|
private val _seasonImages = Collections.synchronizedMap(mutableStateMapOf<Int, MutableMap<Int, ImageCollection>>())
|
||||||
|
val seasonImages: Map<Int, out Map<Int, ImageCollection>>
|
||||||
|
get() = _seasonImages
|
||||||
|
|
||||||
|
private val _seasonVideos = Collections.synchronizedMap(mutableStateMapOf<Int, MutableMap<Int, List<Video>>>())
|
||||||
|
val seasonVideos: MutableMap<Int, out Map<Int, List<Video>>>
|
||||||
|
get() = _seasonVideos
|
||||||
|
|
||||||
|
private val _seasonWatchProviders = Collections.synchronizedMap(mutableStateMapOf<Int, MutableMap<Int, WatchProviders>>())
|
||||||
|
val seasonWatchProviders: MutableMap<Int, out Map<Int, WatchProviders>>
|
||||||
|
get() = _seasonWatchProviders
|
||||||
|
|
||||||
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
val imagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val imagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
val castCrewLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val castCrewLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
@@ -80,6 +108,11 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
val externalIdsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val externalIdsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
val accountStatesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val accountStatesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
val seasonsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
val seasonsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
val seasonAccountStatesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
val seasonCreditsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
val seasonImagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
val seasonVideosLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
val seasonWatchProvidersLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||||
|
|
||||||
override suspend fun getById(id: Int, refreshing: Boolean) {
|
override suspend fun getById(id: Int, refreshing: Boolean) {
|
||||||
loadRemoteData(
|
loadRemoteData(
|
||||||
@@ -169,6 +202,15 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getExternalIds(id: Int, refreshing: Boolean) {
|
||||||
|
loadRemoteData(
|
||||||
|
{ service.getExternalIds(id) },
|
||||||
|
{ externalIds[id] = it },
|
||||||
|
externalIdsLoadingState,
|
||||||
|
refreshing
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getSeason(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
suspend fun getSeason(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||||
loadRemoteData(
|
loadRemoteData(
|
||||||
{ service.getSeason(seriesId, seasonId) },
|
{ service.getSeason(seriesId, seasonId) },
|
||||||
@@ -186,11 +228,78 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getExternalIds(id: Int, refreshing: Boolean) {
|
suspend fun getSeasonAccountStates(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||||
loadRemoteData(
|
loadRemoteData(
|
||||||
{ service.getExternalIds(id) },
|
{ service.getSeasonAccountStates(seriesId, seasonId) },
|
||||||
{ externalIds[id] = it },
|
{ sas ->
|
||||||
externalIdsLoadingState,
|
_seasonAccountStates
|
||||||
|
.getOrPut(seriesId) {
|
||||||
|
emptyMap<Int, SeasonAccountStates>().toMutableMap()
|
||||||
|
}[seasonId] = sas
|
||||||
|
},
|
||||||
|
seasonAccountStatesLoadingState,
|
||||||
|
refreshing
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonCredits(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||||
|
loadRemoteData(
|
||||||
|
{ service.getSeasonCredits(seriesId, seasonId) },
|
||||||
|
{ sc ->
|
||||||
|
_seasonCast
|
||||||
|
.getOrPut(seriesId) {
|
||||||
|
emptyMap<Int, List<TvCastMember>>().toMutableMap()
|
||||||
|
}[seasonId] = sc.cast
|
||||||
|
_seasonCrew
|
||||||
|
.getOrPut(seriesId) {
|
||||||
|
emptyMap<Int, List<TvCrewMember>>().toMutableMap()
|
||||||
|
}[seasonId] = sc.crew
|
||||||
|
},
|
||||||
|
seasonCreditsLoadingState,
|
||||||
|
refreshing
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonImages(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||||
|
loadRemoteData(
|
||||||
|
{ service.getSeasonImages(seriesId, seasonId) },
|
||||||
|
{ si ->
|
||||||
|
_seasonImages
|
||||||
|
.getOrPut(seriesId) {
|
||||||
|
emptyMap<Int, ImageCollection>().toMutableMap()
|
||||||
|
}[seasonId] = si
|
||||||
|
},
|
||||||
|
seasonImagesLoadingState,
|
||||||
|
refreshing
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonVideos(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||||
|
loadRemoteData(
|
||||||
|
{ service.getSeasonVideos(seriesId, seasonId) },
|
||||||
|
{ sv ->
|
||||||
|
_seasonVideos
|
||||||
|
.getOrPut(seriesId) {
|
||||||
|
emptyMap<Int, List<Video>>().toMutableMap()
|
||||||
|
}[seasonId] = sv.results
|
||||||
|
},
|
||||||
|
seasonVideosLoadingState,
|
||||||
|
refreshing
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonWatchProviders(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||||
|
loadRemoteData(
|
||||||
|
{ service.getSeasonWatchProviders(seriesId, seasonId) },
|
||||||
|
{ si ->
|
||||||
|
si.results[Locale.getDefault().country]?.let { wp ->
|
||||||
|
_seasonWatchProviders
|
||||||
|
.getOrPut(seriesId) {
|
||||||
|
emptyMap<Int, WatchProviders>().toMutableMap()
|
||||||
|
}[seasonId] = wp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
seasonWatchProvidersLoadingState,
|
||||||
refreshing
|
refreshing
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class SeasonAccountStatesResultDeserializer: BaseDeserializer<SeasonAccountStatesResult>() {
|
||||||
|
|
||||||
|
override fun processJson(obj: JsonObject): SeasonAccountStatesResult {
|
||||||
|
val id = obj.get("id").asInt
|
||||||
|
val episodeNumber = obj.get("episode_number").asInt
|
||||||
|
return try {
|
||||||
|
val isRated = obj.get("rated").asBoolean
|
||||||
|
SeasonAccountStatesResult(id, episodeNumber, isRated, -1)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val rating = obj.get("rated").asJsonObject.get("value").asInt
|
||||||
|
SeasonAccountStatesResult(id, episodeNumber, true, rating)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,5 +5,7 @@ import com.google.gson.annotations.SerializedName
|
|||||||
data class Image(
|
data class Image(
|
||||||
@SerializedName("file_path") val filePath: String,
|
@SerializedName("file_path") val filePath: String,
|
||||||
@SerializedName("height") val height: Int,
|
@SerializedName("height") val height: Int,
|
||||||
@SerializedName("width") val width: Int
|
@SerializedName("width") val width: Int,
|
||||||
|
@SerializedName("aspect_ratio") val aspectRatio: Float? = null,
|
||||||
|
@SerializedName("iso_639_1") val language: String? = null
|
||||||
)
|
)
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
class PersonImage(
|
|
||||||
@SerializedName("aspect_ratio") val aspectRatio: Float,
|
|
||||||
@SerializedName("file_path") val filePath: String,
|
|
||||||
@SerializedName("width") val width: Int,
|
|
||||||
@SerializedName("height") val height: Int
|
|
||||||
)
|
|
||||||
@@ -3,5 +3,5 @@ package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
|||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
class PersonImageCollection(
|
class PersonImageCollection(
|
||||||
@SerializedName("profiles") val images: List<PersonImage>
|
@SerializedName("profiles") val images: List<Image>
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class SeasonAccountStates(
|
||||||
|
@SerializedName("id") val id: Int,
|
||||||
|
@SerializedName("results") val results: List<SeasonAccountStatesResult>
|
||||||
|
)
|
||||||
|
|
||||||
|
class SeasonAccountStatesResult(
|
||||||
|
@SerializedName("id") val id: Int,
|
||||||
|
@SerializedName("episode_number") val episodeNumber: Int,
|
||||||
|
val isRated: Boolean,
|
||||||
|
var rating: Int
|
||||||
|
)
|
||||||
@@ -21,12 +21,14 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.CreditMediaDeserializ
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.DetailCastDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.DetailCastDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.DetailCrewDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.DetailCrewDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SeasonAccountStatesResultDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SortableSearchResultDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SortableSearchResultDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CreditMedia
|
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.DetailCast
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew
|
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.KnownFor
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SeasonAccountStatesResult
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
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.AccountV4Service
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
|
||||||
@@ -86,7 +88,8 @@ val networkModule = module {
|
|||||||
DetailCast::class.java to DetailCastDeserializer(),
|
DetailCast::class.java to DetailCastDeserializer(),
|
||||||
DetailCrew::class.java to DetailCrewDeserializer(),
|
DetailCrew::class.java to DetailCrewDeserializer(),
|
||||||
CreditMedia::class.java to CreditMediaDeserializer(),
|
CreditMedia::class.java to CreditMediaDeserializer(),
|
||||||
Date::class.java to DateTypeAdapter()
|
Date::class.java to DateTypeAdapter(),
|
||||||
|
SeasonAccountStatesResult::class.java to SeasonAccountStatesResultDeserializer()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ package com.owenlejeune.tvtime.ui.components
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.Divider
|
import androidx.compose.material3.Divider
|
||||||
@@ -28,7 +33,9 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
@@ -47,12 +54,17 @@ import com.google.accompanist.pager.rememberPagerState
|
|||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.LoadingState
|
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.Episode
|
||||||
|
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.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCastMember
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCastMember
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCrewMember
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCrewMember
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Person
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Person
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCastMember
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCastMember
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCrewMember
|
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.listItems
|
||||||
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||||
import com.owenlejeune.tvtime.extensions.toDp
|
import com.owenlejeune.tvtime.extensions.toDp
|
||||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||||
@@ -408,7 +420,8 @@ fun AdditionalDetailItem(
|
|||||||
fun EpisodeItem(
|
fun EpisodeItem(
|
||||||
episode: Episode,
|
episode: Episode,
|
||||||
elevation: Dp = 10.dp,
|
elevation: Dp = 10.dp,
|
||||||
maxDescriptionLines: Int = 2
|
maxDescriptionLines: Int = 2,
|
||||||
|
rating: Int? = null
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
@@ -478,6 +491,14 @@ fun EpisodeItem(
|
|||||||
color = textColor,
|
color = textColor,
|
||||||
maxLines = maxDescriptionLines
|
maxLines = maxDescriptionLines
|
||||||
)
|
)
|
||||||
|
rating?.let {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.your_rating, rating),
|
||||||
|
color = textColor.copy(alpha = 0.8f),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,3 +539,222 @@ fun CastCrewCard(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchProvidersCard(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
providers: WatchProviders
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.watch_providers_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(start = 16.dp, top = 12.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
val itemsMap = mutableMapOf<Int, List<WatchProviderDetails>>().apply {
|
||||||
|
providers.flaterate?.let { put(0, it) }
|
||||||
|
providers.rent?.let { put(1, it) }
|
||||||
|
providers.buy?.let { put(2, it) }
|
||||||
|
}
|
||||||
|
val selected = remember { mutableStateOf(if (itemsMap.isEmpty()) null else itemsMap.values.first()) }
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
PillSegmentedControl(
|
||||||
|
items = itemsMap.values.toList(),
|
||||||
|
itemLabel = { i, _ ->
|
||||||
|
when (i) {
|
||||||
|
0 -> context.getString(R.string.streaming_label)
|
||||||
|
1 -> context.getString(R.string.rent_label)
|
||||||
|
2 -> context.getString(R.string.buy_label)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onItemSelected = { i, _ -> selected.value = itemsMap.values.toList()[i] },
|
||||||
|
modifier = Modifier.padding(all = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Crossfade(
|
||||||
|
modifier = modifier.padding(top = 4.dp, bottom = 12.dp),
|
||||||
|
targetState = selected.value
|
||||||
|
) { value ->
|
||||||
|
WatchProviderContainer(watchProviders = value!!, link = providers.link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchProviderContainer(
|
||||||
|
watchProviders: List<WatchProviderDetails>,
|
||||||
|
link: String
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
com.google.accompanist.flowlayout.FlowRow(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
mainAxisSpacing = 8.dp,
|
||||||
|
crossAxisSpacing = 4.dp
|
||||||
|
) {
|
||||||
|
watchProviders
|
||||||
|
.sortedBy { it.displayPriority }
|
||||||
|
.forEach { item ->
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.clickable {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val url = TmdbUtils.fullLogoPath(item.logoPath)
|
||||||
|
val model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(url)
|
||||||
|
.diskCacheKey(url)
|
||||||
|
.networkCachePolicy(CachePolicy.ENABLED)
|
||||||
|
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||||
|
.build()
|
||||||
|
AsyncImage(
|
||||||
|
model = model,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = item.providerName,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
modifier = Modifier.width(48.dp),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideosCard(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
videos: List<Video>
|
||||||
|
) {
|
||||||
|
ExpandableContentCard(
|
||||||
|
modifier = modifier,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.videos_label),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(start = 12.dp, top = 8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
},
|
||||||
|
toggleTextColor = MaterialTheme.colorScheme.primary
|
||||||
|
) { isExpanded ->
|
||||||
|
VideoGroup(
|
||||||
|
results = videos,
|
||||||
|
type = Video.Type.TRAILER,
|
||||||
|
title = stringResource(id = Video.Type.TRAILER.stringRes)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
Video.Type.values().filter { it != Video.Type.TRAILER }.forEach { type ->
|
||||||
|
VideoGroup(
|
||||||
|
results = videos,
|
||||||
|
type = type,
|
||||||
|
title = stringResource(id = type.stringRes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoGroup(
|
||||||
|
results: List<Video>,
|
||||||
|
type: Video.Type,
|
||||||
|
title: String
|
||||||
|
) {
|
||||||
|
val videos = results.filter { it.isOfficial && it.type == type }
|
||||||
|
if (videos.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(start = 12.dp, top = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
val posterWidth = 120.dp
|
||||||
|
LazyRow(modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
listItems(videos) { video ->
|
||||||
|
FullScreenThumbnailVideoPlayer(
|
||||||
|
key = video.key,
|
||||||
|
title = video.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(posterWidth)
|
||||||
|
.wrapContentHeight()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImagesCard(
|
||||||
|
images: List<Image>,
|
||||||
|
onSeeAll: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
ContentCard(
|
||||||
|
title = stringResource(R.string.images_title)
|
||||||
|
) {
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(vertical = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
items(images) { image ->
|
||||||
|
PosterItem(
|
||||||
|
width = 120.dp,
|
||||||
|
url = TmdbUtils.getFullPersonImagePath(image.filePath),
|
||||||
|
placeholder = Icons.Filled.Person,
|
||||||
|
title = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSeeAll?.let {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.expand_see_all),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp, bottom = 16.dp)
|
||||||
|
.clickable(onClick = onSeeAll)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens
|
package com.owenlejeune.tvtime.ui.screens
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -21,7 +18,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
@@ -70,7 +66,6 @@ import androidx.paging.compose.collectAsLazyPagingItems
|
|||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.pager.PagerState
|
import com.google.accompanist.pager.PagerState
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
@@ -81,15 +76,10 @@ 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.DetailedTv
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Genre
|
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.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCastMember
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCrewMember
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Person
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCastMember
|
|
||||||
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.Video
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderDetails
|
|
||||||
import com.owenlejeune.tvtime.extensions.DateFormat
|
import com.owenlejeune.tvtime.extensions.DateFormat
|
||||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||||
|
import com.owenlejeune.tvtime.extensions.combineWith
|
||||||
import com.owenlejeune.tvtime.extensions.combinedOnVisibilityChange
|
import com.owenlejeune.tvtime.extensions.combinedOnVisibilityChange
|
||||||
import com.owenlejeune.tvtime.extensions.format
|
import com.owenlejeune.tvtime.extensions.format
|
||||||
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
||||||
@@ -116,7 +106,6 @@ import com.owenlejeune.tvtime.ui.components.FullScreenThumbnailVideoPlayer
|
|||||||
import com.owenlejeune.tvtime.ui.components.HtmlText
|
import com.owenlejeune.tvtime.ui.components.HtmlText
|
||||||
import com.owenlejeune.tvtime.ui.components.ImageGalleryOverlay
|
import com.owenlejeune.tvtime.ui.components.ImageGalleryOverlay
|
||||||
import com.owenlejeune.tvtime.ui.components.ListContentCard
|
import com.owenlejeune.tvtime.ui.components.ListContentCard
|
||||||
import com.owenlejeune.tvtime.ui.components.PillSegmentedControl
|
|
||||||
import com.owenlejeune.tvtime.ui.components.PlaceholderDetailHeader
|
import com.owenlejeune.tvtime.ui.components.PlaceholderDetailHeader
|
||||||
import com.owenlejeune.tvtime.ui.components.PlaceholderPosterItem
|
import com.owenlejeune.tvtime.ui.components.PlaceholderPosterItem
|
||||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||||
@@ -124,6 +113,8 @@ import com.owenlejeune.tvtime.ui.components.RoundedChip
|
|||||||
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
||||||
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
||||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||||
|
import com.owenlejeune.tvtime.ui.components.VideosCard
|
||||||
|
import com.owenlejeune.tvtime.ui.components.WatchProvidersCard
|
||||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||||
import com.owenlejeune.tvtime.ui.theme.Typography
|
import com.owenlejeune.tvtime.ui.theme.Typography
|
||||||
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
||||||
@@ -377,7 +368,7 @@ fun MediaViewContent(
|
|||||||
mainViewModel = mainViewModel
|
mainViewModel = mainViewModel
|
||||||
)
|
)
|
||||||
|
|
||||||
VideosCard(
|
VideosArea(
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
mainViewModel = mainViewModel,
|
mainViewModel = mainViewModel,
|
||||||
@@ -386,7 +377,7 @@ fun MediaViewContent(
|
|||||||
|
|
||||||
AdditionalDetailsCard(mediaItem = mediaItem, type = type)
|
AdditionalDetailsCard(mediaItem = mediaItem, type = type)
|
||||||
|
|
||||||
WatchProvidersCard(itemId = itemId, type = type, mainViewModel = mainViewModel)
|
WatchProvidersArea(itemId = itemId, type = type, mainViewModel = mainViewModel)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
mediaItem?.productionCompanies?.firstOrNull { it.name == "Marvel Studios" } != null
|
mediaItem?.productionCompanies?.firstOrNull { it.name == "Marvel Studios" } != null
|
||||||
@@ -891,6 +882,9 @@ private fun SeasonCard(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(all = 12.dp)
|
.padding(all = 12.dp)
|
||||||
|
.clickable {
|
||||||
|
appNavController.navigate(AppNavItem.DetailView.withArgs(MediaViewType.SEASON, itemId.combineWith(it.seasonNumber)))
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
PosterItem(
|
PosterItem(
|
||||||
url = TmdbUtils.getFullPosterPath(it.posterPath),
|
url = TmdbUtils.getFullPosterPath(it.posterPath),
|
||||||
@@ -992,7 +986,7 @@ fun SimilarContentCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideosCard(
|
fun VideosArea(
|
||||||
itemId: Int,
|
itemId: Int,
|
||||||
type: MediaViewType,
|
type: MediaViewType,
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
@@ -1002,74 +996,13 @@ fun VideosCard(
|
|||||||
val videos = videosMap[itemId]
|
val videos = videosMap[itemId]
|
||||||
|
|
||||||
if (videos?.any { it.isOfficial } == true) {
|
if (videos?.any { it.isOfficial } == true) {
|
||||||
ExpandableContentCard(
|
VideosCard(videos = videos, modifier = modifier)
|
||||||
modifier = modifier,
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.videos_label),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
modifier = Modifier.padding(start = 12.dp, top = 8.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
},
|
|
||||||
toggleTextColor = MaterialTheme.colorScheme.primary
|
|
||||||
) { isExpanded ->
|
|
||||||
VideoGroup(
|
|
||||||
results = videos,
|
|
||||||
type = Video.Type.TRAILER,
|
|
||||||
title = stringResource(id = Video.Type.TRAILER.stringRes)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isExpanded) {
|
|
||||||
Video.Type.values().filter { it != Video.Type.TRAILER }.forEach { type ->
|
|
||||||
VideoGroup(
|
|
||||||
results = videos,
|
|
||||||
type = type,
|
|
||||||
title = stringResource(id = type.stringRes)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun VideoGroup(results: List<Video>, type: Video.Type, title: String) {
|
|
||||||
val videos = results.filter { it.isOfficial && it.type == type }
|
|
||||||
if (videos.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = Modifier.padding(start = 12.dp, top = 8.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
val posterWidth = 120.dp
|
|
||||||
LazyRow(modifier = Modifier
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
|
||||||
listItems(videos) { video ->
|
|
||||||
FullScreenThumbnailVideoPlayer(
|
|
||||||
key = video.key,
|
|
||||||
title = video.name,
|
|
||||||
modifier = Modifier
|
|
||||||
.width(posterWidth)
|
|
||||||
.wrapContentHeight()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("AutoboxingStateValueProperty")
|
@SuppressLint("AutoboxingStateValueProperty")
|
||||||
@Composable
|
@Composable
|
||||||
private fun WatchProvidersCard(
|
private fun WatchProvidersArea(
|
||||||
itemId: Int,
|
itemId: Int,
|
||||||
type: MediaViewType,
|
type: MediaViewType,
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
@@ -1079,105 +1012,11 @@ private fun WatchProvidersCard(
|
|||||||
val watchProviders = watchProvidersMap[itemId]
|
val watchProviders = watchProvidersMap[itemId]
|
||||||
watchProviders?.let { providers ->
|
watchProviders?.let { providers ->
|
||||||
if (providers.buy?.isNotEmpty() == true || providers.rent?.isNotEmpty() == true || providers.flaterate?.isNotEmpty() == true) {
|
if (providers.buy?.isNotEmpty() == true || providers.rent?.isNotEmpty() == true || providers.flaterate?.isNotEmpty() == true) {
|
||||||
Card(
|
WatchProvidersCard(providers = providers, modifier = modifier)
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.watch_providers_title),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
modifier = Modifier.padding(start = 16.dp, top = 12.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
val itemsMap = mutableMapOf<Int, List<WatchProviderDetails>>().apply {
|
|
||||||
providers.flaterate?.let { put(0, it) }
|
|
||||||
providers.rent?.let { put(1, it) }
|
|
||||||
providers.buy?.let { put(2, it) }
|
|
||||||
}
|
|
||||||
val selected = remember { mutableStateOf(if (itemsMap.isEmpty()) null else itemsMap.values.first()) }
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
PillSegmentedControl(
|
|
||||||
items = itemsMap.values.toList(),
|
|
||||||
itemLabel = { i, _ ->
|
|
||||||
when (i) {
|
|
||||||
0 -> context.getString(R.string.streaming_label)
|
|
||||||
1 -> context.getString(R.string.rent_label)
|
|
||||||
2 -> context.getString(R.string.buy_label)
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onItemSelected = { i, _ -> selected.value = itemsMap.values.toList()[i] },
|
|
||||||
modifier = Modifier.padding(all = 8.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Crossfade(
|
|
||||||
modifier = modifier.padding(top = 4.dp, bottom = 12.dp),
|
|
||||||
targetState = selected.value
|
|
||||||
) { value ->
|
|
||||||
WatchProviderContainer(watchProviders = value!!, link = providers.link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun WatchProviderContainer(
|
|
||||||
watchProviders: List<WatchProviderDetails>,
|
|
||||||
link: String
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
FlowRow(
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
|
||||||
mainAxisSpacing = 8.dp,
|
|
||||||
crossAxisSpacing = 4.dp
|
|
||||||
) {
|
|
||||||
watchProviders
|
|
||||||
.sortedBy { it.displayPriority }
|
|
||||||
.forEach { item ->
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(10.dp))
|
|
||||||
.clickable {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
val url = TmdbUtils.fullLogoPath(item.logoPath)
|
|
||||||
val model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(url)
|
|
||||||
.diskCacheKey(url)
|
|
||||||
.networkCachePolicy(CachePolicy.ENABLED)
|
|
||||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
|
||||||
.build()
|
|
||||||
AsyncImage(
|
|
||||||
model = model,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(48.dp)
|
|
||||||
.clip(RoundedCornerShape(10.dp))
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = item.providerName,
|
|
||||||
fontSize = 10.sp,
|
|
||||||
modifier = Modifier.width(48.dp),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NextMcuProjectCard(
|
private fun NextMcuProjectCard(
|
||||||
itemId: Int,
|
itemId: Int,
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
@@ -46,8 +45,6 @@ import androidx.navigation.NavController
|
|||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||||
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.Image
|
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.ImageCollection
|
||||||
import com.owenlejeune.tvtime.extensions.DateFormat
|
import com.owenlejeune.tvtime.extensions.DateFormat
|
||||||
@@ -60,6 +57,7 @@ import com.owenlejeune.tvtime.ui.components.ContentCard
|
|||||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||||
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
||||||
import com.owenlejeune.tvtime.ui.components.ExternalIdsArea
|
import com.owenlejeune.tvtime.ui.components.ExternalIdsArea
|
||||||
|
import com.owenlejeune.tvtime.ui.components.ImagesCard
|
||||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||||
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
||||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||||
@@ -193,7 +191,7 @@ fun PersonDetailScreen(
|
|||||||
|
|
||||||
AdditionalDetailsCard(id = personId, mainViewModel = mainViewModel)
|
AdditionalDetailsCard(id = personId, mainViewModel = mainViewModel)
|
||||||
|
|
||||||
ImagesCard(id = personId, appNavController = appNavController)
|
ImagesArea(id = personId, appNavController = appNavController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +329,7 @@ private fun CreditsCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ImagesCard(
|
private fun ImagesArea(
|
||||||
id: Int,
|
id: Int,
|
||||||
appNavController: NavController
|
appNavController: NavController
|
||||||
) {
|
) {
|
||||||
@@ -339,46 +337,12 @@ private fun ImagesCard(
|
|||||||
val imagesMap = remember { mainViewModel.peopleImagesMap }
|
val imagesMap = remember { mainViewModel.peopleImagesMap }
|
||||||
val images = imagesMap[id] ?: emptyList()
|
val images = imagesMap[id] ?: emptyList()
|
||||||
|
|
||||||
ContentCard(
|
ImagesCard(images = images) {
|
||||||
title = stringResource(R.string.images_title)
|
appNavController.navigate(
|
||||||
) {
|
AppNavItem.GalleryView.withArgs(
|
||||||
LazyRow(
|
MediaViewType.PERSON,
|
||||||
modifier = Modifier
|
id
|
||||||
.fillMaxWidth()
|
)
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
|
||||||
items(images) { image ->
|
|
||||||
PosterItem(
|
|
||||||
width = 120.dp,
|
|
||||||
url = TmdbUtils.getFullPersonImagePath(image.filePath),
|
|
||||||
placeholder = Icons.Filled.Person,
|
|
||||||
title = ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.expand_see_all),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 16.dp, bottom = 16.dp)
|
|
||||||
.clickable {
|
|
||||||
appNavController.navigate(
|
|
||||||
AppNavItem.GalleryView.withArgs(
|
|
||||||
MediaViewType.PERSON,
|
|
||||||
id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,10 @@ import com.owenlejeune.tvtime.ui.components.CastCrewCard
|
|||||||
import com.owenlejeune.tvtime.ui.components.ContentCard
|
import com.owenlejeune.tvtime.ui.components.ContentCard
|
||||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||||
import com.owenlejeune.tvtime.ui.components.EpisodeItem
|
import com.owenlejeune.tvtime.ui.components.EpisodeItem
|
||||||
|
import com.owenlejeune.tvtime.ui.components.ImagesCard
|
||||||
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
||||||
|
import com.owenlejeune.tvtime.ui.components.VideosCard
|
||||||
|
import com.owenlejeune.tvtime.ui.components.WatchProvidersCard
|
||||||
import com.owenlejeune.tvtime.ui.theme.Typography
|
import com.owenlejeune.tvtime.ui.theme.Typography
|
||||||
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
||||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||||
@@ -52,6 +55,11 @@ private fun fetchData(
|
|||||||
) {
|
) {
|
||||||
val scope = CoroutineScope(Dispatchers.IO)
|
val scope = CoroutineScope(Dispatchers.IO)
|
||||||
scope.launch { mainViewModel.getSeason(seriesId, seasonNumber, force) }
|
scope.launch { mainViewModel.getSeason(seriesId, seasonNumber, force) }
|
||||||
|
scope.launch { mainViewModel.getSeasonAccountStates(seriesId, seasonNumber, force) }
|
||||||
|
scope.launch { mainViewModel.getSeasonImages(seriesId, seasonNumber, force) }
|
||||||
|
scope.launch { mainViewModel.getSeasonVideos(seriesId, seasonNumber, force) }
|
||||||
|
scope.launch { mainViewModel.getSeasonCredits(seriesId, seasonNumber, force) }
|
||||||
|
scope.launch { mainViewModel.getSeasonWatchProviders(seriesId, seasonNumber, force) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -86,7 +94,14 @@ fun SeasonDetailsScreen(
|
|||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(modifier = Modifier.padding(innerPadding)) {
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
SeasonContent(appNavController = appNavController, season = season)
|
season?.let {
|
||||||
|
SeasonContent(
|
||||||
|
seriesId = seriesId,
|
||||||
|
appNavController = appNavController,
|
||||||
|
mainViewModel = mainViewModel,
|
||||||
|
season = season
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,8 +109,10 @@ fun SeasonDetailsScreen(
|
|||||||
@OptIn(ExperimentalPagerApi::class)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun SeasonContent(
|
private fun SeasonContent(
|
||||||
|
seriesId: Int,
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
season: Season?
|
mainViewModel: MainViewModel,
|
||||||
|
season: Season
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -104,7 +121,7 @@ private fun SeasonContent(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
DetailHeader(
|
DetailHeader(
|
||||||
posterUrl = TmdbUtils.getFullPosterPath(season?.posterPath),
|
posterUrl = TmdbUtils.getFullPosterPath(season.posterPath),
|
||||||
elevation = 0.dp,
|
elevation = 0.dp,
|
||||||
expandedPosterAsBackdrop = true
|
expandedPosterAsBackdrop = true
|
||||||
)
|
)
|
||||||
@@ -115,7 +132,7 @@ private fun SeasonContent(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = season?.name ?: "",
|
text = season.name,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = Typography.headlineLarge,
|
style = Typography.headlineLarge,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
@@ -123,8 +140,32 @@ private fun SeasonContent(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
season?.episodes?.forEach { episode ->
|
val accountStatesMap = remember { mainViewModel.tvSeasonAccountStates }
|
||||||
SeasonEpisodeItem(appNavController = appNavController, episode = episode)
|
val accountStates = accountStatesMap[seriesId]?.get(season.seasonNumber)
|
||||||
|
|
||||||
|
season.episodes.forEach { episode ->
|
||||||
|
val rating = accountStates?.results?.find { it.id == episode.id }?.takeUnless { !it.isRated }?.rating
|
||||||
|
SeasonEpisodeItem(appNavController = appNavController, episode = episode, rating = rating)
|
||||||
|
}
|
||||||
|
|
||||||
|
val imagesMap = remember { mainViewModel.tvSeasonImages }
|
||||||
|
val images = imagesMap[seriesId]?.get(season.seasonNumber)
|
||||||
|
images?.let {
|
||||||
|
ImagesCard(images = images.posters)
|
||||||
|
}
|
||||||
|
|
||||||
|
val videosMap = remember { mainViewModel.tvSeasonVideos }
|
||||||
|
val videos = videosMap[seriesId]?.get(season.seasonNumber)
|
||||||
|
if (videos?.any { it.isOfficial } == true) {
|
||||||
|
VideosCard(videos = videos, modifier = Modifier.fillMaxWidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
val watchProvidersMap = remember { mainViewModel.tvSeasonWatchProviders }
|
||||||
|
val watchProviders = watchProvidersMap[seriesId]?.get(season.seasonNumber)
|
||||||
|
watchProviders?.let { providers ->
|
||||||
|
if (providers.buy?.isNotEmpty() == true || providers.rent?.isNotEmpty() == true || providers.flaterate?.isNotEmpty() == true) {
|
||||||
|
WatchProvidersCard(providers = providers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +176,16 @@ private fun SeasonContent(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SeasonEpisodeItem(
|
private fun SeasonEpisodeItem(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
episode: Episode
|
episode: Episode,
|
||||||
|
rating: Int?
|
||||||
) {
|
) {
|
||||||
ContentCard {
|
ContentCard {
|
||||||
EpisodeItem(episode = episode, elevation = 0.dp, maxDescriptionLines = 5)
|
EpisodeItem(
|
||||||
|
episode = episode,
|
||||||
|
elevation = 0.dp,
|
||||||
|
maxDescriptionLines = 5,
|
||||||
|
rating = rating
|
||||||
|
)
|
||||||
|
|
||||||
episode.guestStars?.let { guestStars ->
|
episode.guestStars?.let { guestStars ->
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ class MainViewModel: ViewModel(), KoinComponent {
|
|||||||
val similarTv = tvService.similar
|
val similarTv = tvService.similar
|
||||||
val tvAccountStates = tvService.accountStates
|
val tvAccountStates = tvService.accountStates
|
||||||
val tvKeywordResults = tvService.keywordResults
|
val tvKeywordResults = tvService.keywordResults
|
||||||
|
val tvSeasonAccountStates = tvService.seasonAccountStates
|
||||||
|
val tvSeasonCast = tvService.seasonCast
|
||||||
|
val tvSeasonCrew = tvService.seasonCrew
|
||||||
|
val tvSeasonImages = tvService.seasonImages
|
||||||
|
val tvSeasonVideos = tvService.seasonVideos
|
||||||
|
val tvSeasonWatchProviders = tvService.seasonWatchProviders
|
||||||
|
|
||||||
val tvDetailsLoadingState = tvService.detailsLoadingState
|
val tvDetailsLoadingState = tvService.detailsLoadingState
|
||||||
val tvImagesLoadingState = tvService.imagesLoadingState
|
val tvImagesLoadingState = tvService.imagesLoadingState
|
||||||
@@ -115,6 +121,11 @@ class MainViewModel: ViewModel(), KoinComponent {
|
|||||||
val tvSeasonsLoadingState = tvService.seasonsLoadingState
|
val tvSeasonsLoadingState = tvService.seasonsLoadingState
|
||||||
val tvContentRatingsLoadingState = tvService.contentRatingsLoadingState
|
val tvContentRatingsLoadingState = tvService.contentRatingsLoadingState
|
||||||
val tvAccountStatesLoadingState = tvService.accountStatesLoadingState
|
val tvAccountStatesLoadingState = tvService.accountStatesLoadingState
|
||||||
|
val tvSeasonAccountStatesLoadingState = tvService.seasonAccountStatesLoadingState
|
||||||
|
val tvSeasonCreditsLoadingState = tvService.seasonCreditsLoadingState
|
||||||
|
val tvSeasonImagesLoadingState = tvService.seasonImagesLoadingState
|
||||||
|
val tvSeasonVideosLoadingState = tvService.seasonVideosLoadingState
|
||||||
|
val tvSeasonWatchProvidersLoadingState = tvService.seasonWatchProvidersLoadingState
|
||||||
|
|
||||||
val popularTv by lazy {
|
val popularTv by lazy {
|
||||||
createPagingFlow(
|
createPagingFlow(
|
||||||
@@ -455,6 +466,39 @@ class MainViewModel: ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonAccountStates(seriesId: Int, seasonId: Int, force: Boolean = false) {
|
||||||
|
if (tvSeasonAccountStates[seriesId]?.get(seasonId) == null || force) {
|
||||||
|
tvService.getSeasonAccountStates(seriesId, seasonId, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonImages(seriesId: Int, seasonId: Int, force: Boolean = false) {
|
||||||
|
if (tvSeasonImages[seriesId]?.get(seasonId) == null || force) {
|
||||||
|
tvService.getSeasonImages(seriesId, seasonId, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonVideos(seriesId: Int, seasonId: Int, force: Boolean = false) {
|
||||||
|
if (tvSeasonVideos[seriesId]?.get(seasonId) == null || force) {
|
||||||
|
tvService.getSeasonVideos(seriesId, seasonId, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonCredits(seriesId: Int, seasonId: Int, force: Boolean = false) {
|
||||||
|
if (tvSeasonCast[seriesId]?.get(seasonId) == null ||
|
||||||
|
tvSeasonCrew[seriesId]?.get(seasonId) == null ||
|
||||||
|
force
|
||||||
|
) {
|
||||||
|
tvService.getSeasonCredits(seriesId, seasonId, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasonWatchProviders(seriesId: Int, seasonId: Int, force: Boolean = false) {
|
||||||
|
if (tvSeasonWatchProviders[seriesId]?.get(seasonId) == null || force) {
|
||||||
|
tvService.getSeasonWatchProviders(seriesId, seasonId, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ComposableNaming")
|
@SuppressLint("ComposableNaming")
|
||||||
@Composable
|
@Composable
|
||||||
fun monitorDetailsLoadingRefreshing(refreshing: MutableState<Boolean>) {
|
fun monitorDetailsLoadingRefreshing(refreshing: MutableState<Boolean>) {
|
||||||
|
|||||||
@@ -262,4 +262,5 @@
|
|||||||
<string name="date_of_death">Date of death</string>
|
<string name="date_of_death">Date of death</string>
|
||||||
<string name="unexpected_error">An unexpected error occurred</string>
|
<string name="unexpected_error">An unexpected error occurred</string>
|
||||||
<string name="guest_stars_label">Guest stars</string>
|
<string name="guest_stars_label">Guest stars</string>
|
||||||
|
<string name="your_rating">Your rating: %1$d/10</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user