fix up some season loading stuff

This commit is contained in:
Owen LeJeune
2023-07-30 22:11:33 -04:00
parent 084a22dd7f
commit 314e4ca964
6 changed files with 88 additions and 18 deletions

View File

@@ -64,7 +64,7 @@ interface MoviesApi {
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds> suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
@GET("movie/{id}/account_states") @GET("movie/{id}/account_states")
suspend fun getAccountStates(@Path("id") id: Int, @Query("session_id") sessionId: String): Response<AccountStates> suspend fun getAccountStates(@Path("id") id: Int): Response<AccountStates>
@GET("discover/movie") @GET("discover/movie")
suspend fun discover(@Query("with_keywords") keywords: String? = null, @Query("page") page: Int): Response<SearchResult<SearchResultMovie>> suspend fun discover(@Query("with_keywords") keywords: String? = null, @Query("page") page: Int): Response<SearchResult<SearchResultMovie>>

View File

@@ -148,9 +148,8 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
} }
override suspend fun getAccountStates(id: Int) { override suspend fun getAccountStates(id: Int) {
val sessionId = SessionManager.currentSession.value?.sessionId ?: throw Exception("Session must not be null")
loadRemoteData( loadRemoteData(
{ movieService.getAccountStates(id, sessionId) }, { movieService.getAccountStates(id) },
{ accountStates[id] = it }, { accountStates[id] = it },
accountStatesLoadingState, accountStatesLoadingState,
false false

View File

@@ -145,7 +145,9 @@ private fun fetchData(
scope.launch { mainViewModel.getVideos(itemId, type, force) } scope.launch { mainViewModel.getVideos(itemId, type, force) }
scope.launch { mainViewModel.getWatchProviders(itemId, type, force) } scope.launch { mainViewModel.getWatchProviders(itemId, type, force) }
scope.launch { mainViewModel.getReviews(itemId, type, force) } scope.launch { mainViewModel.getReviews(itemId, type, force) }
scope.launch { mainViewModel.getAccountStates(itemId, type) } if (SessionManager.isLoggedIn) {
scope.launch { mainViewModel.getAccountStates(itemId, type) }
}
when (type) { when (type) {
MediaViewType.MOVIE -> { MediaViewType.MOVIE -> {
@@ -190,6 +192,9 @@ fun MediaDetailScreen(
if (type == MediaViewType.TV) { if (type == MediaViewType.TV) {
LaunchedEffect(mediaItem) { LaunchedEffect(mediaItem) {
val lastSeason = (mediaItem as DetailedTv?)?.numberOfSeasons ?: 0 val lastSeason = (mediaItem as DetailedTv?)?.numberOfSeasons ?: 0
// for (i in lastSeason downTo 0) {
// mainViewModel.getSeason(itemId, i)
// }
if (lastSeason > 0) { if (lastSeason > 0) {
mainViewModel.getSeason(itemId, lastSeason) mainViewModel.getSeason(itemId, lastSeason)
} }
@@ -871,7 +876,7 @@ private fun SeasonCard(
appNavController: NavController appNavController: NavController
) { ) {
val seasonsMap = remember { mainViewModel.tvSeasons } val seasonsMap = remember { mainViewModel.tvSeasons }
val lastSeason = seasonsMap[itemId]?.lastOrNull() val lastSeason = seasonsMap[itemId]?.maxByOrNull { it.seasonNumber }
lastSeason?.let { lastSeason?.let {
ContentCard( ContentCard(

View File

@@ -1,6 +1,11 @@
package com.owenlejeune.tvtime.ui.screens package com.owenlejeune.tvtime.ui.screens
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -9,17 +14,26 @@ import androidx.compose.foundation.layout.Spacer
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.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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
@@ -29,6 +43,7 @@ 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.Episode import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Episode
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.extensions.toCompositeParts import com.owenlejeune.tvtime.extensions.toCompositeParts
import com.owenlejeune.tvtime.ui.components.BackButton import com.owenlejeune.tvtime.ui.components.BackButton
import com.owenlejeune.tvtime.ui.components.CastCrewCard import com.owenlejeune.tvtime.ui.components.CastCrewCard
@@ -42,6 +57,7 @@ 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
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -55,7 +71,9 @@ 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) } if (SessionManager.isLoggedIn) {
scope.launch { mainViewModel.getSeasonAccountStates(seriesId, seasonNumber, force) }
}
scope.launch { mainViewModel.getSeasonImages(seriesId, seasonNumber, force) } scope.launch { mainViewModel.getSeasonImages(seriesId, seasonNumber, force) }
scope.launch { mainViewModel.getSeasonVideos(seriesId, seasonNumber, force) } scope.launch { mainViewModel.getSeasonVideos(seriesId, seasonNumber, force) }
scope.launch { mainViewModel.getSeasonCredits(seriesId, seasonNumber, force) } scope.launch { mainViewModel.getSeasonCredits(seriesId, seasonNumber, force) }
@@ -126,26 +144,61 @@ private fun SeasonContent(
expandedPosterAsBackdrop = true expandedPosterAsBackdrop = true
) )
var isExpanded by remember { mutableStateOf(true) }
Column( Column(
modifier = Modifier modifier = Modifier
.padding(start = 16.dp, end = 16.dp), .padding(start = 16.dp, end = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( Row {
text = season.name, Text(
color = MaterialTheme.colorScheme.secondary, text = season.name,
style = Typography.headlineLarge, color = MaterialTheme.colorScheme.secondary,
maxLines = 2, style = Typography.headlineLarge,
overflow = TextOverflow.Ellipsis, maxLines = 2,
modifier = Modifier.fillMaxWidth() overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.weight(1f))
var currentRotation by remember { mutableFloatStateOf(180f) }
val rotation = remember { Animatable(currentRotation) }
LaunchedEffect(isExpanded) {
rotation.animateTo(
targetValue = if (isExpanded) 180f else 0f,
animationSpec = tween(200, easing = LinearEasing)
) {
currentRotation = value
}
}
Icon(
imageVector = Icons.Outlined.ExpandMore,
contentDescription = null,
modifier = Modifier
.size(48.dp)
.rotate(currentRotation)
.clickable {
isExpanded = !isExpanded
},
tint = MaterialTheme.colorScheme.onSurface,
)
}
val accountStatesMap = remember { mainViewModel.tvSeasonAccountStates } val accountStatesMap = remember { mainViewModel.tvSeasonAccountStates }
val accountStates = accountStatesMap[seriesId]?.get(season.seasonNumber) val accountStates = accountStatesMap[seriesId]?.get(season.seasonNumber)
season.episodes.forEach { episode -> AnimatedVisibility(
val rating = accountStates?.results?.find { it.id == episode.id }?.takeUnless { !it.isRated }?.rating visible = isExpanded
SeasonEpisodeItem(appNavController = appNavController, episode = episode, rating = rating) ) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
season.episodes.forEach { episode ->
DrawEpisodeCard(
episode = episode,
accountStates = accountStates,
appNavController = appNavController
)
}
}
} }
val imagesMap = remember { mainViewModel.tvSeasonImages } val imagesMap = remember { mainViewModel.tvSeasonImages }
@@ -173,6 +226,16 @@ private fun SeasonContent(
} }
} }
@Composable
private fun DrawEpisodeCard(
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)
}
@Composable @Composable
private fun SeasonEpisodeItem( private fun SeasonEpisodeItem(
appNavController: NavController, appNavController: NavController,

View File

@@ -102,7 +102,7 @@ fun SeasonListScreen(
appNavController = appNavController, appNavController = appNavController,
seriesId = id, seriesId = id,
season = season, season = season,
expandedByDefault = index == 0 expandedByDefault = index == 1
) )
} }

View File

@@ -29,6 +29,9 @@ object SessionManager: KoinComponent {
val currentSession = mutableStateOf<Session?>(null) val currentSession = mutableStateOf<Session?>(null)
val isLoggedIn: Boolean
get() = currentSession.value?.isAuthorized == true
class AuthorizedSessionValues( class AuthorizedSessionValues(
@SerializedName("session_id") val sessionId: String, @SerializedName("session_id") val sessionId: String,
@SerializedName("access_token") val accessToken: String, @SerializedName("access_token") val accessToken: String,