mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 04:32:43 -05:00
add pull to refresh to details screens
This commit is contained in:
@@ -4,6 +4,7 @@ enum class LoadingState {
|
||||
|
||||
INACTIVE,
|
||||
LOADING,
|
||||
REFRESHING,
|
||||
COMPLETE,
|
||||
ERROR
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.owenlejeune.tvtime.api
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import retrofit2.Response
|
||||
|
||||
infix fun <T> Response<T>.storedIn(body: (T) -> Unit) {
|
||||
@@ -8,4 +9,24 @@ infix fun <T> Response<T>.storedIn(body: (T) -> Unit) {
|
||||
body(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> loadRemoteData(
|
||||
fetcher: suspend () -> Response<T>,
|
||||
processor: (T) -> Unit,
|
||||
loadSate: MutableState<LoadingState>,
|
||||
refreshing: Boolean
|
||||
) {
|
||||
loadSate.value = if (refreshing) LoadingState.REFRESHING else LoadingState.LOADING
|
||||
val response = fetcher()
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
processor(it)
|
||||
loadSate.value = LoadingState.COMPLETE
|
||||
} ?: run {
|
||||
loadSate.value = LoadingState.ERROR
|
||||
}
|
||||
} else {
|
||||
loadSate.value = LoadingState.ERROR
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,27 @@ import retrofit2.Response
|
||||
|
||||
interface DetailService {
|
||||
|
||||
suspend fun getById(id: Int)
|
||||
suspend fun getById(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getImages(id: Int)
|
||||
suspend fun getImages(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getCastAndCrew(id: Int)
|
||||
suspend fun getCastAndCrew(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse>
|
||||
|
||||
suspend fun getVideos(id: Int)
|
||||
suspend fun getVideos(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getReviews(id: Int)
|
||||
suspend fun getReviews(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun postRating(id: Int, ratingBody: RatingBody)
|
||||
|
||||
suspend fun deleteRating(id: Int)
|
||||
|
||||
suspend fun getKeywords(id: Int)
|
||||
suspend fun getKeywords(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getWatchProviders(id: Int)
|
||||
suspend fun getWatchProviders(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getExternalIds(id: Int)
|
||||
suspend fun getExternalIds(id: Int, refreshing: Boolean)
|
||||
|
||||
suspend fun getAccountStates(id: Int)
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.owenlejeune.tvtime.api.LoadingState
|
||||
import com.owenlejeune.tvtime.api.loadRemoteData
|
||||
import com.owenlejeune.tvtime.api.storedIn
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastMember
|
||||
@@ -58,62 +60,112 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||
val accountStates = Collections.synchronizedMap(mutableStateMapOf<Int, AccountStates>())
|
||||
val keywordResults = Collections.synchronizedMap(mutableStateMapOf<Int, Flow<PagingData<SearchResultMedia>>>())
|
||||
|
||||
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val imagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val castCrewLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val videosLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val reviewsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val keywordsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val watchProvidersLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val externalIdsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val releaseDatesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val accountStatesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
|
||||
override suspend fun getById(id: Int) {
|
||||
movieService.getMovieById(id) storedIn { detailMovies[id] = it }
|
||||
override suspend fun getById(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getMovieById(id) },
|
||||
{ detailMovies[id] = it },
|
||||
detailsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getImages(id: Int) {
|
||||
movieService.getMovieImages(id) storedIn { images[id] = it }
|
||||
override suspend fun getImages(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getMovieImages(id) },
|
||||
{ images[id] = it },
|
||||
imagesLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCastAndCrew(id: Int) {
|
||||
movieService.getCastAndCrew(id) storedIn {
|
||||
cast[id] = it.cast
|
||||
crew[id] = it.crew
|
||||
}
|
||||
override suspend fun getCastAndCrew(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getCastAndCrew(id) },
|
||||
{
|
||||
cast[id] = it.cast
|
||||
crew[id] = it.crew
|
||||
},
|
||||
castCrewLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
override suspend fun getVideos(id: Int) {
|
||||
movieService.getVideos(id) storedIn { videos[id] = it.results }
|
||||
override suspend fun getVideos(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getVideos(id) },
|
||||
{ videos[id] = it.results },
|
||||
videosLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getReviews(id: Int) {
|
||||
movieService.getReviews(id) storedIn { reviews[id] = it.results }
|
||||
override suspend fun getReviews(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getReviews(id) },
|
||||
{ reviews[id] = it.results },
|
||||
reviewsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getKeywords(id: Int) {
|
||||
movieService.getKeywords(id) storedIn { keywords[id] = it.keywords ?: emptyList() }
|
||||
override suspend fun getKeywords(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getKeywords(id) },
|
||||
{ keywords[id] = it.keywords ?: emptyList() },
|
||||
keywordsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getWatchProviders(id: Int) {
|
||||
movieService.getWatchProviders(id) storedIn {
|
||||
it.results[Locale.getDefault().country]?.let { wp ->
|
||||
watchProviders[id] = wp
|
||||
}
|
||||
}
|
||||
override suspend fun getWatchProviders(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getWatchProviders(id) },
|
||||
{
|
||||
it.results[Locale.getDefault().country]?.let { wp ->
|
||||
watchProviders[id] = wp
|
||||
}
|
||||
},
|
||||
watchProvidersLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getExternalIds(id: Int) {
|
||||
movieService.getExternalIds(id) storedIn { externalIds[id] = it }
|
||||
override suspend fun getExternalIds(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getExternalIds(id) },
|
||||
{ externalIds[id] = it },
|
||||
externalIdsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAccountStates(id: Int) {
|
||||
val sessionId = SessionManager.currentSession.value?.sessionId ?: throw Exception("Session must not be null")
|
||||
val response = movieService.getAccountStates(id, sessionId)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Log.d(TAG, "Successfully got account states: $it")
|
||||
accountStates[id] = it
|
||||
} ?: run {
|
||||
Log.d(TAG, "Problem getting account states")
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Issue getting account states: $response")
|
||||
}
|
||||
loadRemoteData(
|
||||
{ movieService.getAccountStates(id, sessionId) },
|
||||
{ accountStates[id] = it },
|
||||
accountStatesLoadingState,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getReleaseDates(id: Int) {
|
||||
movieService.getReleaseDates(id) storedIn { releaseDates[id] = it.releaseDates }
|
||||
suspend fun getReleaseDates(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ movieService.getReleaseDates(id) },
|
||||
{ releaseDates[id] = it.releaseDates },
|
||||
releaseDatesLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun postRating(id: Int, ratingBody: RatingBody) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.owenlejeune.tvtime.api.LoadingState
|
||||
import com.owenlejeune.tvtime.api.loadRemoteData
|
||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCast
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew
|
||||
@@ -28,61 +30,48 @@ class PeopleService: KoinComponent {
|
||||
val imagesMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<PersonImage>>())
|
||||
val externalIdsMap = Collections.synchronizedMap(mutableStateMapOf<Int, ExternalIds>())
|
||||
|
||||
suspend fun getPerson(id: Int) {
|
||||
val response = service.getPerson(id)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Log.d(TAG, "Successfully got person $id")
|
||||
peopleMap[id] = it
|
||||
} ?: run {
|
||||
Log.w(TAG, "Problem getting person $id")
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Issue getting person $id")
|
||||
}
|
||||
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val castCrewLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val imagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val externalIdsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
|
||||
suspend fun getPerson(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getPerson(id) },
|
||||
{ peopleMap[id] = it },
|
||||
detailsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getCredits(id: Int) {
|
||||
val response = service.getCredits(id)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Log.d(TAG, "Successfully got credits $id")
|
||||
suspend fun getCredits(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getCredits(id) },
|
||||
{
|
||||
castMap[id] = it.cast
|
||||
crewMap[id] = it.crew
|
||||
} ?: run {
|
||||
Log.w(TAG, "Problem getting credits $id")
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Issue getting credits $id")
|
||||
}
|
||||
},
|
||||
castCrewLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getImages(id: Int) {
|
||||
val response = service.getImages(id)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Log.d(TAG, "Successfully got images $id")
|
||||
imagesMap[id] = it.images
|
||||
} ?: run {
|
||||
Log.w(TAG, "Problem getting images $id")
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Issues getting images $id")
|
||||
}
|
||||
suspend fun getImages(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getImages(id) },
|
||||
{ imagesMap[id] = it.images },
|
||||
imagesLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getExternalIds(id: Int) {
|
||||
val response = service.getExternalIds(id)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Log.d(TAG, "Successfully got external ids $id")
|
||||
externalIdsMap[id] = it
|
||||
} ?: run {
|
||||
Log.w(TAG, "Problem getting external ids $id")
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Issue getting external ids $id")
|
||||
}
|
||||
suspend fun getExternalIds(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getExternalIds(id) },
|
||||
{ externalIdsMap[id] = it },
|
||||
externalIdsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getPopular(page: Int): Response<HomePagePeopleResponse> {
|
||||
|
||||
@@ -2,9 +2,12 @@ package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.owenlejeune.tvtime.api.LoadingState
|
||||
import com.owenlejeune.tvtime.api.loadRemoteData
|
||||
import com.owenlejeune.tvtime.api.storedIn
|
||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||
@@ -66,59 +69,126 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
val seasons: MutableMap<Int, out Set<Season>>
|
||||
get() = _seasons
|
||||
|
||||
override suspend fun getById(id: Int) {
|
||||
service.getTvShowById(id) storedIn { detailTv[id] = it }
|
||||
val detailsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val imagesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val castCrewLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val contentRatingsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val videosLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val reviewsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val keywordsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val watchProvidersLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val externalIdsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val accountStatesLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
val seasonsLoadingState = mutableStateOf(LoadingState.INACTIVE)
|
||||
|
||||
override suspend fun getById(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getTvShowById(id) },
|
||||
{ detailTv[id] = it },
|
||||
detailsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getImages(id: Int) {
|
||||
service.getTvImages(id) storedIn { images[id] = it }
|
||||
override suspend fun getImages(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getTvImages(id) },
|
||||
{ images[id] = it },
|
||||
imagesLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCastAndCrew(id: Int) {
|
||||
service.getCastAndCrew(id) storedIn {
|
||||
cast[id] = it.cast
|
||||
crew[id] = it.crew
|
||||
}
|
||||
override suspend fun getCastAndCrew(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getCastAndCrew(id) },
|
||||
{
|
||||
cast[id] = it.cast
|
||||
crew[id] = it.crew
|
||||
},
|
||||
castCrewLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getContentRatings(id: Int) {
|
||||
service.getContentRatings(id) storedIn { contentRatings[id] = it.results }
|
||||
suspend fun getContentRatings(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getContentRatings(id) },
|
||||
{ contentRatings[id] = it.results },
|
||||
contentRatingsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getVideos(id: Int) {
|
||||
service.getVideos(id) storedIn { videos[id] = it.results }
|
||||
override suspend fun getVideos(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getVideos(id) },
|
||||
{ videos[id] = it.results },
|
||||
videosLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getReviews(id: Int) {
|
||||
service.getReviews(id) storedIn { reviews[id] = it.results }
|
||||
override suspend fun getReviews(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getReviews(id) },
|
||||
{ reviews[id] = it.results },
|
||||
reviewsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getKeywords(id: Int) {
|
||||
service.getKeywords(id) storedIn { keywords[id] = it.keywords ?: emptyList() }
|
||||
override suspend fun getKeywords(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getKeywords(id) },
|
||||
{ keywords[id] = it.keywords ?: emptyList() },
|
||||
keywordsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getWatchProviders(id: Int) {
|
||||
service.getWatchProviders(id) storedIn {
|
||||
it.results[Locale.getDefault().country]?.let { wp ->
|
||||
watchProviders[id] = wp
|
||||
}
|
||||
}
|
||||
override suspend fun getWatchProviders(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getWatchProviders(id) },
|
||||
{
|
||||
it.results[Locale.getDefault().country]?.let { wp ->
|
||||
watchProviders[id] = wp
|
||||
}
|
||||
},
|
||||
watchProvidersLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAccountStates(id: Int) {
|
||||
service.getAccountStates(id) storedIn { accountStates[id] = it }
|
||||
loadRemoteData(
|
||||
{ service.getAccountStates(id) },
|
||||
{ accountStates[id] = it },
|
||||
accountStatesLoadingState,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getSeason(seriesId: Int, seasonId: Int) {
|
||||
service.getSeason(seriesId, seasonId) storedIn {
|
||||
_seasons[seriesId]?.add(it) ?: run {
|
||||
_seasons[seriesId] = mutableSetOf(it)
|
||||
}
|
||||
}
|
||||
suspend fun getSeason(seriesId: Int, seasonId: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getSeason(seriesId, seasonId) },
|
||||
{
|
||||
_seasons[seriesId]?.add(it) ?: run {
|
||||
_seasons[seriesId] = mutableSetOf(it)
|
||||
}
|
||||
},
|
||||
seasonsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getExternalIds(id: Int) {
|
||||
service.getExternalIds(id) storedIn { externalIds[id] = it }
|
||||
override suspend fun getExternalIds(id: Int, refreshing: Boolean) {
|
||||
loadRemoteData(
|
||||
{ service.getExternalIds(id) },
|
||||
{ externalIds[id] = it },
|
||||
externalIdsLoadingState,
|
||||
refreshing
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun postRating(id: Int, ratingBody: RatingBody) {
|
||||
|
||||
@@ -6,4 +6,6 @@ import kotlinx.coroutines.launch
|
||||
|
||||
fun Any.coroutineTask(runnable: suspend () -> Unit) {
|
||||
CoroutineScope(Dispatchers.IO).launch { runnable() }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> anyOf(vararg items: T, predicate: (T) -> Boolean): Boolean = items.any(predicate)
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -252,11 +253,13 @@ private fun ExternalIdLogo(
|
||||
context.startActivity(intent)
|
||||
},
|
||||
modifier = Modifier.size(28.dp)
|
||||
// modifier = Modifier.size(40.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = logoPainter,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,56 @@ package com.owenlejeune.tvtime.ui.screens
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Movie
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -39,7 +78,18 @@ import com.google.accompanist.pager.PagerState
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
||||
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.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.WatchProviderDetails
|
||||
import com.owenlejeune.tvtime.extensions.DateFormat
|
||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||
import com.owenlejeune.tvtime.extensions.format
|
||||
@@ -47,17 +97,63 @@ import com.owenlejeune.tvtime.extensions.getCalendarYear
|
||||
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.*
|
||||
import com.owenlejeune.tvtime.ui.components.ActionsView
|
||||
import com.owenlejeune.tvtime.ui.components.AvatarImage
|
||||
import com.owenlejeune.tvtime.ui.components.ChipDefaults
|
||||
import com.owenlejeune.tvtime.ui.components.ChipGroup
|
||||
import com.owenlejeune.tvtime.ui.components.ChipInfo
|
||||
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.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.RoundedChip
|
||||
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
||||
import com.owenlejeune.tvtime.ui.components.SelectableTextChip
|
||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.SpecialFeaturesViewModel
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.java.KoinJavaComponent.get
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
||||
private suspend fun fetchData(
|
||||
mainViewModel: MainViewModel,
|
||||
itemId: Int,
|
||||
type: MediaViewType,
|
||||
force: Boolean = false
|
||||
) {
|
||||
mainViewModel.getById(itemId, type, force)
|
||||
mainViewModel.getImages(itemId, type, force)
|
||||
mainViewModel.getExternalIds(itemId, type, force)
|
||||
mainViewModel.getKeywords(itemId, type, force)
|
||||
mainViewModel.getCastAndCrew(itemId, type, force)
|
||||
mainViewModel.getSimilar(itemId, type)
|
||||
mainViewModel.getVideos(itemId, type, force)
|
||||
mainViewModel.getWatchProviders(itemId, type, force)
|
||||
mainViewModel.getReviews(itemId, type, force)
|
||||
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> {
|
||||
mainViewModel.getReleaseDates(itemId, force)
|
||||
}
|
||||
MediaViewType.TV -> {
|
||||
mainViewModel.getContentRatings(itemId, force)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun MediaDetailScreen(
|
||||
appNavController: NavController,
|
||||
@@ -65,17 +161,14 @@ fun MediaDetailScreen(
|
||||
type: MediaViewType,
|
||||
windowSize: WindowSizeClass
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
|
||||
val systemUiController = rememberSystemUiController()
|
||||
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
||||
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getById(itemId, type)
|
||||
mainViewModel.getImages(itemId, type)
|
||||
}
|
||||
|
||||
val mediaItems: Map<Int, DetailedItem> = remember { mainViewModel.produceDetailsFor(type) }
|
||||
val mediaItem = mediaItems[itemId]
|
||||
|
||||
@@ -87,6 +180,30 @@ fun MediaDetailScreen(
|
||||
|
||||
val pagerState = rememberPagerState(initialPage = 0)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
fetchData(mainViewModel, itemId, type)
|
||||
}
|
||||
|
||||
if (type == MediaViewType.TV) {
|
||||
LaunchedEffect(mediaItem) {
|
||||
val lastSeason = (mediaItem as DetailedTv?)?.numberOfSeasons ?: 0
|
||||
if (lastSeason > 0) {
|
||||
mainViewModel.getSeason(itemId, lastSeason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isRefreshing = remember { mutableStateOf(false) }
|
||||
mainViewModel.monitorDetailsLoadingRefreshing(refreshing = isRefreshing)
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing.value,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
fetchData(mainViewModel, itemId, type, true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
@@ -116,7 +233,10 @@ fun MediaDetailScreen(
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
Box(modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(state = pullRefreshState)
|
||||
) {
|
||||
MediaViewContent(
|
||||
appNavController = appNavController,
|
||||
itemId = itemId,
|
||||
@@ -128,6 +248,11 @@ fun MediaDetailScreen(
|
||||
pagerState = pagerState,
|
||||
mainViewModel = mainViewModel
|
||||
)
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing.value,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(alignment = Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +268,7 @@ fun MediaDetailScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@OptIn(ExperimentalPagerApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun MediaViewContent(
|
||||
appNavController: NavController,
|
||||
@@ -157,10 +282,6 @@ private fun MediaViewContent(
|
||||
pagerState: PagerState,
|
||||
preferences: AppPreferences = get(AppPreferences::class.java)
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getExternalIds(itemId, type)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.background),
|
||||
@@ -290,9 +411,6 @@ private fun MiscTvDetails(
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
mediaItem?.let { tv ->
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getContentRatings(itemId)
|
||||
}
|
||||
val series = tv as DetailedTv
|
||||
|
||||
val contentRatingsMap = remember { mainViewModel.tvContentRatings }
|
||||
@@ -318,10 +436,6 @@ private fun MiscMovieDetails(
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
mediaItem?.let { mi ->
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getReleaseDates(itemId)
|
||||
}
|
||||
|
||||
val movie = mi as DetailedMovie
|
||||
|
||||
val contentRatingsMap = remember { mainViewModel.movieReleaseDates }
|
||||
@@ -388,10 +502,6 @@ private fun OverviewCard(
|
||||
appNavController: NavController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getKeywords(itemId, type)
|
||||
}
|
||||
|
||||
val keywordsMap = remember { mainViewModel.produceKeywordsFor(type) }
|
||||
val keywords = keywordsMap[itemId]
|
||||
|
||||
@@ -602,10 +712,6 @@ private fun CastCard(
|
||||
appNavController: NavController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getCastAndCrew(itemId, type)
|
||||
}
|
||||
|
||||
val castMap = remember { mainViewModel.produceCastFor(type) }
|
||||
val cast = castMap[itemId]
|
||||
|
||||
@@ -694,13 +800,6 @@ private fun SeasonCard(
|
||||
mainViewModel: MainViewModel,
|
||||
appNavController: NavController
|
||||
) {
|
||||
LaunchedEffect(mediaItem) {
|
||||
val lastSeason = (mediaItem as DetailedTv?)?.numberOfSeasons ?: 0
|
||||
if (lastSeason > 0) {
|
||||
mainViewModel.getSeason(itemId, lastSeason)
|
||||
}
|
||||
}
|
||||
|
||||
val seasonsMap = remember { mainViewModel.tvSeasons }
|
||||
val lastSeason = seasonsMap[itemId]?.lastOrNull()
|
||||
|
||||
@@ -766,10 +865,6 @@ fun SimilarContentCard(
|
||||
appNavController: NavController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getSimilar(itemId, mediaType)
|
||||
}
|
||||
|
||||
val similarContentMap = remember { mainViewModel.produceSimilarContentFor(mediaType) }
|
||||
val similarContent = similarContentMap[itemId]
|
||||
val pagingItems = similarContent?.collectAsLazyPagingItems()
|
||||
@@ -822,10 +917,6 @@ fun VideosCard(
|
||||
mainViewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getVideos(itemId, type)
|
||||
}
|
||||
|
||||
val videosMap = remember { mainViewModel.produceVideosFor(type) }
|
||||
val videos = videosMap[itemId]
|
||||
|
||||
@@ -902,10 +993,6 @@ private fun WatchProvidersCard(
|
||||
mainViewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getWatchProviders(itemId, type)
|
||||
}
|
||||
|
||||
val watchProvidersMap = remember { mainViewModel.produceWatchProvidersFor(type) }
|
||||
val watchProviders = watchProvidersMap[itemId]
|
||||
watchProviders?.let { providers ->
|
||||
@@ -1116,10 +1203,6 @@ private fun ReviewsCard(
|
||||
mainViewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getReviews(itemId, type)
|
||||
}
|
||||
|
||||
val reviewsMap = remember { mainViewModel.produceReviewsFor(type) }
|
||||
val reviews = reviewsMap[itemId]
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import android.graphics.Paint.Align
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -14,9 +15,13 @@ import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -28,7 +33,10 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -51,22 +59,33 @@ import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.Integer.min
|
||||
|
||||
private const val TAG = "PeopleDetailScreen"
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
||||
private suspend fun fetchData(
|
||||
mainViewModel: MainViewModel,
|
||||
id: Int,
|
||||
force: Boolean = false
|
||||
) {
|
||||
mainViewModel.getById(id, MediaViewType.PERSON, force)
|
||||
mainViewModel.getExternalIds(id, MediaViewType.PERSON, force)
|
||||
mainViewModel.getCastAndCrew(id, MediaViewType.PERSON, force)
|
||||
mainViewModel.getImages(id, MediaViewType.PERSON, force)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun PersonDetailScreen(
|
||||
appNavController: NavController,
|
||||
personId: Int
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
LaunchedEffect(Unit) {
|
||||
mainViewModel.getById(personId, MediaViewType.PERSON)
|
||||
mainViewModel.getExternalIds(personId, MediaViewType.PERSON)
|
||||
mainViewModel.getCastAndCrew(personId, MediaViewType.PERSON)
|
||||
mainViewModel.getImages(personId, MediaViewType.PERSON)
|
||||
fetchData(mainViewModel, personId)
|
||||
}
|
||||
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@@ -79,6 +98,17 @@ fun PersonDetailScreen(
|
||||
val topAppBarScrollState = rememberTopAppBarState()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
|
||||
|
||||
val isRefreshing = remember { mutableStateOf(false) }
|
||||
mainViewModel.monitorDetailsLoadingRefreshing(refreshing = isRefreshing)
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing.value,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
fetchData(mainViewModel, personId, true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
@@ -102,7 +132,10 @@ fun PersonDetailScreen(
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
Box(modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.pullRefresh(state = pullRefreshState)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.background)
|
||||
@@ -131,6 +164,12 @@ fun PersonDetailScreen(
|
||||
|
||||
ImagesCard(id = personId, appNavController = appNavController)
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing.value,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(alignment = Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +296,12 @@ private fun ImagesCard(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
.clickable {
|
||||
appNavController.navigate(AppNavItem.GalleryView.withArgs(MediaViewType.PERSON, id))
|
||||
appNavController.navigate(
|
||||
AppNavItem.GalleryView.withArgs(
|
||||
MediaViewType.PERSON,
|
||||
id
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ private fun MovieSearchResultView(
|
||||
service: MoviesService = get(MoviesService::class.java)
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
service.getCastAndCrew(result.id)
|
||||
service.getCastAndCrew(result.id, false)
|
||||
}
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
val castMap = remember { mainViewModel.movieCast }
|
||||
@@ -428,7 +428,7 @@ private fun TvSearchResultView(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
service.getCastAndCrew(result.id)
|
||||
service.getCastAndCrew(result.id, false)
|
||||
}
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
val castMap = remember { mainViewModel.tvCast }
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.owenlejeune.tvtime.ui.viewmodel
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.PagingData
|
||||
import com.owenlejeune.tvtime.api.LoadingState
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.createPagingFlow
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
||||
@@ -19,6 +24,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.anyOf
|
||||
import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabNavItem
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||
@@ -47,6 +53,17 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val movieAccountStates = movieService.accountStates
|
||||
val movieKeywordResults = movieService.keywordResults
|
||||
|
||||
val movieDetailsLoadingState = movieService.detailsLoadingState
|
||||
val movieImagesLoadingState = movieService.imagesLoadingState
|
||||
val movieCastCrewLoadingState = movieService.castCrewLoadingState
|
||||
val movieVideosLoadingState = movieService.videosLoadingState
|
||||
val movieReviewsLoadingState = movieService.reviewsLoadingState
|
||||
val movieKeywordsLoadingState = movieService.keywordsLoadingState
|
||||
val movieWatchProvidersLoadingState = movieService.watchProvidersLoadingState
|
||||
val movieExternalIdsLoadingState = movieService.externalIdsLoadingState
|
||||
val movieReleaseDatesLoadingState = movieService.releaseDatesLoadingState
|
||||
val movieAccountStatesLoadingState = movieService.accountStatesLoadingState
|
||||
|
||||
val popularMovies by lazy {
|
||||
createPagingFlow(
|
||||
fetcher = { p -> movieService.getPopular(p) },
|
||||
@@ -87,6 +104,18 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val tvAccountStates = tvService.accountStates
|
||||
val tvKeywordResults = tvService.keywordResults
|
||||
|
||||
val tvDetailsLoadingState = tvService.detailsLoadingState
|
||||
val tvImagesLoadingState = tvService.imagesLoadingState
|
||||
val tvCastCrewLoadingState = tvService.castCrewLoadingState
|
||||
val tvVideosLoadingState = tvService.videosLoadingState
|
||||
val tvReviewsLoadingState = tvService.reviewsLoadingState
|
||||
val tvKeywordsLoadingState = tvService.keywordsLoadingState
|
||||
val tvWatchProvidersLoadingState = tvService.watchProvidersLoadingState
|
||||
val tvExternalIdsLoadingState = tvService.externalIdsLoadingState
|
||||
val tvSeasonsLoadingState = tvService.seasonsLoadingState
|
||||
val tvContentRatingsLoadingState = tvService.contentRatingsLoadingState
|
||||
val tvAccountStatesLoadingState = tvService.accountStatesLoadingState
|
||||
|
||||
val popularTv by lazy {
|
||||
createPagingFlow(
|
||||
fetcher = { p -> tvService.getPopular(p) },
|
||||
@@ -118,6 +147,11 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val peopleImagesMap = peopleService.imagesMap
|
||||
val peopleExternalIdsMap = peopleService.externalIdsMap
|
||||
|
||||
val peopleDetailsLoadingState = peopleService.detailsLoadingState
|
||||
val peopleCastCrewLoadingState = peopleService.castCrewLoadingState
|
||||
val peopleImagesLoadingState = peopleService.imagesLoadingState
|
||||
val peopleExternalIdsLoadingState = peopleService.externalIdsLoadingState
|
||||
|
||||
val popularPeople by lazy {
|
||||
createPagingFlow(
|
||||
fetcher = { p -> peopleService.getPopular(p) },
|
||||
@@ -231,68 +265,68 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
|
||||
suspend fun getById(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (detailMovies[id] == null || force) movieService.getById(id)
|
||||
MediaViewType.TV -> if (detailedTv[id] == null || force) tvService.getById(id)
|
||||
MediaViewType.PERSON -> if (peopleMap[id] == null || force) peopleService.getPerson(id)
|
||||
MediaViewType.MOVIE -> if (detailMovies[id] == null || force) movieService.getById(id, force)
|
||||
MediaViewType.TV -> if (detailedTv[id] == null || force) tvService.getById(id, force)
|
||||
MediaViewType.PERSON -> if (peopleMap[id] == null || force) peopleService.getPerson(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getImages(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieImages[id] == null || force) movieService.getImages(id)
|
||||
MediaViewType.TV -> if (tvImages[id] == null || force) tvService.getImages(id)
|
||||
MediaViewType.PERSON -> if (peopleImagesMap[id] == null || force) peopleService.getImages(id)
|
||||
MediaViewType.MOVIE -> if (movieImages[id] == null || force) movieService.getImages(id, force)
|
||||
MediaViewType.TV -> if (tvImages[id] == null || force) tvService.getImages(id, force)
|
||||
MediaViewType.PERSON -> if (peopleImagesMap[id] == null || force) peopleService.getImages(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCastAndCrew(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieCast[id] == null || movieCrew[id] == null || force) movieService.getCastAndCrew(id)
|
||||
MediaViewType.TV -> if (tvCast[id] == null || tvCrew[id] == null || force) tvService.getCastAndCrew(id)
|
||||
MediaViewType.PERSON -> if (peopleCastMap[id] == null || peopleCrewMap[id] == null || force) peopleService.getCredits(id)
|
||||
MediaViewType.MOVIE -> if (movieCast[id] == null || movieCrew[id] == null || force) movieService.getCastAndCrew(id, force)
|
||||
MediaViewType.TV -> if (tvCast[id] == null || tvCrew[id] == null || force) tvService.getCastAndCrew(id, force)
|
||||
MediaViewType.PERSON -> if (peopleCastMap[id] == null || peopleCrewMap[id] == null || force) peopleService.getCredits(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getVideos(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieVideos[id] == null || force) movieService.getVideos(id)
|
||||
MediaViewType.TV -> if (tvVideos[id] == null || force) tvService.getVideos(id)
|
||||
MediaViewType.MOVIE -> if (movieVideos[id] == null || force) movieService.getVideos(id, force)
|
||||
MediaViewType.TV -> if (tvVideos[id] == null || force) tvService.getVideos(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getReviews(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieReviews[id] == null || force) movieService.getReviews(id)
|
||||
MediaViewType.TV -> if (tvReviews[id] == null || force) tvService.getReviews(id)
|
||||
MediaViewType.MOVIE -> if (movieReviews[id] == null || force) movieService.getReviews(id, force)
|
||||
MediaViewType.TV -> if (tvReviews[id] == null || force) tvService.getReviews(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getKeywords(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieKeywords[id] == null || force) movieService.getKeywords(id)
|
||||
MediaViewType.TV -> if (tvKeywords[id] == null || force) tvService.getKeywords(id)
|
||||
MediaViewType.MOVIE -> if (movieKeywords[id] == null || force) movieService.getKeywords(id, force)
|
||||
MediaViewType.TV -> if (tvKeywords[id] == null || force) tvService.getKeywords(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWatchProviders(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieWatchProviders[id] == null || force) movieService.getWatchProviders(id)
|
||||
MediaViewType.TV -> if (tvWatchProviders[id] == null || force) tvService.getWatchProviders(id)
|
||||
MediaViewType.MOVIE -> if (movieWatchProviders[id] == null || force) movieService.getWatchProviders(id, force)
|
||||
MediaViewType.TV -> if (tvWatchProviders[id] == null || force) tvService.getWatchProviders(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getExternalIds(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> if (movieExternalIds[id] == null || force) movieService.getExternalIds(id)
|
||||
MediaViewType.TV -> if (tvExternalIds[id] == null || force) tvService.getExternalIds(id)
|
||||
MediaViewType.PERSON -> if (peopleExternalIdsMap[id] == null || force) peopleService.getExternalIds(id)
|
||||
MediaViewType.MOVIE -> if (movieExternalIds[id] == null || force) movieService.getExternalIds(id, force)
|
||||
MediaViewType.TV -> if (tvExternalIds[id] == null || force) tvService.getExternalIds(id, force)
|
||||
MediaViewType.PERSON -> if (peopleExternalIdsMap[id] == null || force) peopleService.getExternalIds(id, force)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -359,20 +393,64 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
|
||||
suspend fun getReleaseDates(id: Int, force: Boolean = false) {
|
||||
if (movieReleaseDates[id] == null || force) {
|
||||
movieService.getReleaseDates(id)
|
||||
movieService.getReleaseDates(id, force)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getContentRatings(id: Int, force: Boolean = false) {
|
||||
if (tvContentRatings[id] == null || force) {
|
||||
tvService.getContentRatings(id)
|
||||
tvService.getContentRatings(id, force)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSeason(seriesId: Int, seasonId: Int, force: Boolean = false) {
|
||||
if (tvSeasons[seriesId] == null || force) {
|
||||
tvService.getSeason(seriesId, seasonId)
|
||||
tvService.getSeason(seriesId, seasonId, force)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ComposableNaming")
|
||||
@Composable
|
||||
fun monitorDetailsLoadingRefreshing(refreshing: MutableState<Boolean>) {
|
||||
val movieDetails = remember { movieDetailsLoadingState }
|
||||
val movieImages = remember { movieImagesLoadingState }
|
||||
val movieCastCrew = remember { movieCastCrewLoadingState }
|
||||
val movieVideos = remember { movieVideosLoadingState }
|
||||
val movieReviews = remember { movieReviewsLoadingState }
|
||||
val movieKeywords = remember { movieKeywordsLoadingState }
|
||||
val movieWatchProviders = remember { movieWatchProvidersLoadingState }
|
||||
val movieExternalIds = remember { movieExternalIdsLoadingState }
|
||||
val movieReleaseDates = remember { movieReleaseDatesLoadingState }
|
||||
val movieAccountStates = remember { movieAccountStatesLoadingState }
|
||||
val tvDetails = remember { tvDetailsLoadingState }
|
||||
val tvImages = remember { tvImagesLoadingState }
|
||||
val tvCastCrew = remember { tvCastCrewLoadingState }
|
||||
val tvVideos = remember { tvVideosLoadingState }
|
||||
val tvReviews = remember { tvReviewsLoadingState }
|
||||
val tvKeywords = remember { tvKeywordsLoadingState }
|
||||
val tvWatchProviders = remember { tvWatchProvidersLoadingState }
|
||||
val tvExternalIds = remember { tvExternalIdsLoadingState }
|
||||
val tvSeasons = remember { tvSeasonsLoadingState }
|
||||
val tvContentRatings = remember { tvContentRatingsLoadingState }
|
||||
val tvAccountStates = remember { tvAccountStatesLoadingState }
|
||||
val peopleDetails = remember { peopleDetailsLoadingState }
|
||||
val peopleCastCrew = remember { peopleCastCrewLoadingState }
|
||||
val peopleImages = remember { peopleImagesLoadingState }
|
||||
val peopleExternalIds = remember { peopleExternalIdsLoadingState }
|
||||
|
||||
refreshing.value = movieDetails.value == LoadingState.REFRESHING || movieImages.value == LoadingState.REFRESHING ||
|
||||
movieCastCrew.value == LoadingState.REFRESHING || movieVideos.value == LoadingState.REFRESHING ||
|
||||
movieReviews.value == LoadingState.REFRESHING || movieKeywords.value == LoadingState.REFRESHING ||
|
||||
movieWatchProviders.value == LoadingState.REFRESHING || movieExternalIds.value == LoadingState.REFRESHING ||
|
||||
movieReleaseDates.value == LoadingState.REFRESHING || movieAccountStates.value == LoadingState.REFRESHING ||
|
||||
tvDetails.value == LoadingState.REFRESHING || tvImages.value == LoadingState.REFRESHING ||
|
||||
tvCastCrew.value == LoadingState.REFRESHING || tvVideos.value == LoadingState.REFRESHING ||
|
||||
tvReviews.value == LoadingState.REFRESHING || tvKeywords.value == LoadingState.REFRESHING ||
|
||||
tvWatchProviders.value == LoadingState.REFRESHING || tvExternalIds.value == LoadingState.REFRESHING ||
|
||||
tvSeasons.value == LoadingState.REFRESHING || tvContentRatings.value == LoadingState.REFRESHING ||
|
||||
tvAccountStates.value == LoadingState.REFRESHING || peopleDetails.value == LoadingState.REFRESHING ||
|
||||
peopleCastCrew.value == LoadingState.REFRESHING || peopleImages.value == LoadingState.REFRESHING ||
|
||||
peopleExternalIds.value == LoadingState.REFRESHING
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user