mirror of
https://github.com/owenlejeune/TVTime.git
synced 2026-02-16 10:16:49 -05:00
begin skeleton loading imeplementation
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.owenlejeune.tvtime.api
|
package com.owenlejeune.tvtime.api
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
infix fun <T> Response<T>.storedIn(body: (T) -> Unit) {
|
infix fun <T> Response<T>.storedIn(body: (T) -> Unit) {
|
||||||
|
|||||||
@@ -5,4 +5,17 @@ import com.google.gson.annotations.SerializedName
|
|||||||
data class Genre(
|
data class Genre(
|
||||||
@SerializedName("id") val id: Int,
|
@SerializedName("id") val id: Int,
|
||||||
@SerializedName("name") val name: String
|
@SerializedName("name") val name: String
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val placeholderData: List<Genre> by lazy {
|
||||||
|
listOf<Genre>().toMutableList().apply {
|
||||||
|
for (i in 0 until 3) {
|
||||||
|
add(Genre(i, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,3 +9,5 @@ fun Any.coroutineTask(runnable: suspend () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> anyOf(vararg items: T, predicate: (T) -> Boolean): Boolean = items.any(predicate)
|
fun <T> anyOf(vararg items: T, predicate: (T) -> Boolean): Boolean = items.any(predicate)
|
||||||
|
|
||||||
|
fun <T: Any> T.isIn(vararg items: T): Boolean = items.any { it == this }
|
||||||
@@ -17,7 +17,10 @@ import androidx.compose.ui.graphics.RectangleShape
|
|||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.TileMode
|
import androidx.compose.ui.graphics.TileMode
|
||||||
|
|
||||||
fun Modifier.shimmerBackground(shape: Shape = RectangleShape): Modifier = composed {
|
fun Modifier.shimmerBackground(
|
||||||
|
shape: Shape = RectangleShape,
|
||||||
|
tint: Color = Color.LightGray
|
||||||
|
): Modifier = composed {
|
||||||
val transition = rememberInfiniteTransition()
|
val transition = rememberInfiniteTransition()
|
||||||
val translateAnimation by transition.animateFloat(
|
val translateAnimation by transition.animateFloat(
|
||||||
initialValue = 0f,
|
initialValue = 0f,
|
||||||
@@ -28,8 +31,8 @@ fun Modifier.shimmerBackground(shape: Shape = RectangleShape): Modifier = compos
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
val shimmerColors = listOf(
|
val shimmerColors = listOf(
|
||||||
Color.LightGray.copy(alpha = 0.9f),
|
tint.copy(alpha = 0.9f),
|
||||||
Color.LightGray.copy(alpha = 0.4f),
|
tint.copy(alpha = 0.4f),
|
||||||
)
|
)
|
||||||
val brush = Brush.linearGradient(
|
val brush = Brush.linearGradient(
|
||||||
colors = shimmerColors,
|
colors = shimmerColors,
|
||||||
|
|||||||
@@ -29,10 +29,14 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.owenlejeune.tvtime.api.LoadingState
|
||||||
|
import com.owenlejeune.tvtime.extensions.isIn
|
||||||
|
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||||
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
|
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
|
||||||
import com.owenlejeune.tvtime.ui.theme.RatingSelected
|
import com.owenlejeune.tvtime.ui.theme.RatingSelected
|
||||||
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
|
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
|
||||||
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
|
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
|
||||||
|
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.types.MediaViewType
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ 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.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
@@ -23,6 +26,7 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
@@ -32,9 +36,14 @@ import com.google.accompanist.pager.HorizontalPager
|
|||||||
import com.google.accompanist.pager.PagerState
|
import com.google.accompanist.pager.PagerState
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
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.tmdb.api.v3.model.ExternalIds
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||||
|
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||||
|
import com.owenlejeune.tvtime.extensions.toDp
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -54,7 +63,9 @@ fun DetailHeader(
|
|||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.then(
|
modifier = modifier.then(
|
||||||
Modifier.fillMaxWidth().wrapContentHeight()
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (imageCollection != null) {
|
if (imageCollection != null) {
|
||||||
@@ -119,7 +130,8 @@ private fun BackdropContainer(
|
|||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.matchParentSize()
|
.width(sizeImage.value.width.toDp())
|
||||||
|
.height(sizeImage.value.height.toDp())
|
||||||
.background(gradient)
|
.background(gradient)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -214,10 +226,35 @@ fun BackdropGallery(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExternalIdsArea(
|
fun ExternalIdsArea(
|
||||||
externalIds: ExternalIds,
|
type: MediaViewType,
|
||||||
|
itemId: Int,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
if (externalIds.hasExternalIds()) {
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
|
val loadingState = remember { mainViewModel.produceExternalIdsLoadingStateFor(type) }
|
||||||
|
|
||||||
|
val externalIdsMap = remember { mainViewModel.produceExternalIdsFor(type) }
|
||||||
|
val externalIds = externalIdsMap[itemId]
|
||||||
|
|
||||||
|
if (loadingState.value == LoadingState.LOADING || loadingState.value == LoadingState.REFRESHING) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(5.dp))
|
||||||
|
.size(28.dp)
|
||||||
|
.shimmerBackground(
|
||||||
|
RoundedCornerShape(5.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (externalIds?.hasExternalIds() == true) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -265,3 +302,31 @@ private fun ExternalIdLogo(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaceholderDetailHeader() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.aspectRatio(1.778f)
|
||||||
|
.shimmerBackground()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
|
PlaceholderPosterItem()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.size(60.dp)
|
||||||
|
.shimmerBackground(tint = MaterialTheme.colorScheme.secondary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -172,14 +172,15 @@ fun PosterItem(
|
|||||||
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
|
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
|
||||||
Card(
|
Card(
|
||||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = elevation),
|
elevation = CardDefaults.elevatedCardElevation(defaultElevation = elevation),
|
||||||
modifier = modifier
|
modifier = modifier.then(Modifier
|
||||||
.width(width = width)
|
.width(width = width)
|
||||||
.height(height = height)
|
.height(height = height)
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.clickable(
|
.clickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
),
|
)
|
||||||
|
),
|
||||||
shape = RoundedCornerShape(5.dp),
|
shape = RoundedCornerShape(5.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.Transparent)
|
colors = CardDefaults.cardColors(containerColor = Color.Transparent)
|
||||||
) {
|
) {
|
||||||
@@ -259,3 +260,20 @@ fun PosterItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaceholderPosterItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
width: Dp = POSTER_WIDTH,
|
||||||
|
height: Dp = POSTER_HEIGHT,
|
||||||
|
tint: Color = Color.LightGray
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier.then(Modifier
|
||||||
|
.width(width)
|
||||||
|
.height(height)
|
||||||
|
.clip(RoundedCornerShape(5.dp))
|
||||||
|
.shimmerBackground(RoundedCornerShape(5.dp), tint)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ import coil.request.ImageRequest
|
|||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails
|
||||||
|
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||||
import com.owenlejeune.tvtime.extensions.toDp
|
import com.owenlejeune.tvtime.extensions.toDp
|
||||||
import com.owenlejeune.tvtime.extensions.unlessEmpty
|
import com.owenlejeune.tvtime.extensions.unlessEmpty
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
@@ -285,7 +286,17 @@ class ChipInfo(
|
|||||||
val text: String,
|
val text: String,
|
||||||
val enabled: Boolean = true,
|
val enabled: Boolean = true,
|
||||||
val id: Int? = null
|
val id: Int? = null
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
fun generatePlaceholders(size: Int): List<ChipInfo> {
|
||||||
|
return emptyList<ChipInfo>().toMutableList().apply {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
add(ChipInfo(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class ChipStyle(val mainAxisSpacing: Dp, val crossAxisSpacing: Dp) {
|
sealed class ChipStyle(val mainAxisSpacing: Dp, val crossAxisSpacing: Dp) {
|
||||||
object Boxy: ChipStyle(8.dp, 4.dp)
|
object Boxy: ChipStyle(8.dp, 4.dp)
|
||||||
@@ -338,15 +349,17 @@ fun BoxyChip(
|
|||||||
style: TextStyle = MaterialTheme.typography.bodySmall,
|
style: TextStyle = MaterialTheme.typography.bodySmall,
|
||||||
isSelected: Boolean = true,
|
isSelected: Boolean = true,
|
||||||
onSelectionChanged: (ChipInfo) -> Unit = {},
|
onSelectionChanged: (ChipInfo) -> Unit = {},
|
||||||
colors: ChipColors = ChipDefaults.boxyChipColors()
|
colors: ChipColors = ChipDefaults.boxyChipColors(),
|
||||||
|
isLoading: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
val shimmerModifier = if (isLoading) Modifier.shimmerBackground(shape = RoundedCornerShape(5.dp)) else Modifier
|
||||||
Surface(
|
Surface(
|
||||||
shadowElevation = 2.dp,
|
shadowElevation = 2.dp,
|
||||||
shape = RoundedCornerShape(5.dp),
|
shape = RoundedCornerShape(5.dp),
|
||||||
color = if (isSelected) colors.selectedContainerColor() else colors.unselectedContainerColor()
|
color = if (isSelected) colors.selectedContainerColor() else colors.unselectedContainerColor()
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = shimmerModifier
|
||||||
.toggleable(
|
.toggleable(
|
||||||
value = isSelected,
|
value = isSelected,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
@@ -367,21 +380,25 @@ fun BoxyChip(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoundedChip(
|
fun RoundedChip(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
chipInfo: ChipInfo,
|
chipInfo: ChipInfo,
|
||||||
style: TextStyle = MaterialTheme.typography.bodySmall,
|
style: TextStyle = MaterialTheme.typography.bodySmall,
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
onSelectionChanged: (ChipInfo) -> Unit = {},
|
onSelectionChanged: (ChipInfo) -> Unit = {},
|
||||||
colors: ChipColors = ChipDefaults.roundedChipColors()
|
colors: ChipColors = ChipDefaults.roundedChipColors(),
|
||||||
|
isLoading: Boolean = false
|
||||||
) {
|
) {
|
||||||
val borderColor = if (isSelected) colors.selectedContainerColor() else colors.unselectedContainerColor()
|
val borderColor = if (isSelected) colors.selectedContainerColor() else colors.unselectedContainerColor()
|
||||||
val radius = style.fontSize.value.dp * 2
|
val radius = style.fontSize.value.dp * 2
|
||||||
Surface(
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
border = BorderStroke(width = 1.dp, borderColor),
|
border = BorderStroke(width = 1.dp, borderColor),
|
||||||
shape = RoundedCornerShape(radius),
|
shape = RoundedCornerShape(radius),
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant
|
color = MaterialTheme.colorScheme.surfaceVariant
|
||||||
) {
|
) {
|
||||||
|
val shimmerModifier = if(isLoading) Modifier.shimmerBackground(RoundedCornerShape(radius)) else Modifier
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = shimmerModifier
|
||||||
.toggleable(
|
.toggleable(
|
||||||
value = isSelected,
|
value = isSelected,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
@@ -403,6 +420,7 @@ fun RoundedChip(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ChipGroup(
|
fun ChipGroup(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
isLoading: Boolean = false,
|
||||||
chips: List<ChipInfo> = emptyList(),
|
chips: List<ChipInfo> = emptyList(),
|
||||||
onSelectedChanged: (ChipInfo) -> Unit = {},
|
onSelectedChanged: (ChipInfo) -> Unit = {},
|
||||||
chipStyle: ChipStyle = ChipStyle.Boxy,
|
chipStyle: ChipStyle = ChipStyle.Boxy,
|
||||||
@@ -411,24 +429,26 @@ fun ChipGroup(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawChip(chipStyle: ChipStyle, chip: ChipInfo) {
|
fun DrawChip(chipStyle: ChipStyle, chip: ChipInfo, isLoading: Boolean) {
|
||||||
when (chipStyle) {
|
when (chipStyle) {
|
||||||
ChipStyle.Boxy -> {
|
ChipStyle.Boxy -> {
|
||||||
BoxyChip(
|
BoxyChip(
|
||||||
chipInfo = chip,
|
chipInfo = chip,
|
||||||
onSelectionChanged = onSelectedChanged,
|
onSelectionChanged = onSelectedChanged,
|
||||||
colors = boxyChipColors
|
colors = boxyChipColors,
|
||||||
|
isLoading = isLoading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChipStyle.Rounded -> {
|
ChipStyle.Rounded -> {
|
||||||
RoundedChip(
|
RoundedChip(
|
||||||
chipInfo = chip,
|
chipInfo = chip,
|
||||||
onSelectionChanged = onSelectedChanged,
|
onSelectionChanged = onSelectedChanged,
|
||||||
colors = roundedChipColors
|
colors = roundedChipColors,
|
||||||
|
isLoading = isLoading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ChipStyle.Mixed -> {
|
is ChipStyle.Mixed -> {
|
||||||
DrawChip(chipStyle = chipStyle.predicate(chip), chip = chip)
|
DrawChip(chipStyle = chipStyle.predicate(chip), chip = chip, isLoading = isLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,7 +459,7 @@ fun ChipGroup(
|
|||||||
mainAxisSpacing = chipStyle.mainAxisSpacing
|
mainAxisSpacing = chipStyle.mainAxisSpacing
|
||||||
) {
|
) {
|
||||||
chips.forEach { chip ->
|
chips.forEach { chip ->
|
||||||
DrawChip(chipStyle = chipStyle, chip = chip)
|
DrawChip(chipStyle = chipStyle, chip = chip, isLoading = isLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
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.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -73,6 +72,7 @@ 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
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
|
import com.owenlejeune.tvtime.api.LoadingState
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
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.DetailedMovie
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedTv
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedTv
|
||||||
@@ -89,8 +89,10 @@ import com.owenlejeune.tvtime.extensions.DateFormat
|
|||||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||||
import com.owenlejeune.tvtime.extensions.format
|
import com.owenlejeune.tvtime.extensions.format
|
||||||
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
||||||
|
import com.owenlejeune.tvtime.extensions.isIn
|
||||||
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
||||||
import com.owenlejeune.tvtime.extensions.listItems
|
import com.owenlejeune.tvtime.extensions.listItems
|
||||||
|
import com.owenlejeune.tvtime.extensions.shimmerBackground
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
import com.owenlejeune.tvtime.ui.components.ActionsView
|
import com.owenlejeune.tvtime.ui.components.ActionsView
|
||||||
import com.owenlejeune.tvtime.ui.components.AvatarImage
|
import com.owenlejeune.tvtime.ui.components.AvatarImage
|
||||||
@@ -109,6 +111,8 @@ 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.PillSegmentedControl
|
||||||
|
import com.owenlejeune.tvtime.ui.components.PlaceholderDetailHeader
|
||||||
|
import com.owenlejeune.tvtime.ui.components.PlaceholderPosterItem
|
||||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||||
import com.owenlejeune.tvtime.ui.components.RoundedChip
|
import com.owenlejeune.tvtime.ui.components.RoundedChip
|
||||||
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
||||||
@@ -121,31 +125,36 @@ import com.owenlejeune.tvtime.ui.viewmodel.SpecialFeaturesViewModel
|
|||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.java.KoinJavaComponent.get
|
import org.koin.java.KoinJavaComponent
|
||||||
|
|
||||||
private suspend fun fetchData(
|
private fun fetchData(
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
itemId: Int,
|
itemId: Int,
|
||||||
type: MediaViewType,
|
type: MediaViewType,
|
||||||
force: Boolean = false
|
force: Boolean = false
|
||||||
) {
|
) {
|
||||||
mainViewModel.getById(itemId, type, force)
|
val scope = CoroutineScope(Dispatchers.IO)
|
||||||
mainViewModel.getImages(itemId, type, force)
|
|
||||||
mainViewModel.getExternalIds(itemId, type, force)
|
scope.launch { mainViewModel.getById(itemId, type, force) }
|
||||||
mainViewModel.getKeywords(itemId, type, force)
|
scope.launch { mainViewModel.getImages(itemId, type, force) }
|
||||||
mainViewModel.getCastAndCrew(itemId, type, force)
|
scope.launch { mainViewModel.getExternalIds(itemId, type, force) }
|
||||||
mainViewModel.getSimilar(itemId, type)
|
scope.launch { mainViewModel.getKeywords(itemId, type, force) }
|
||||||
mainViewModel.getVideos(itemId, type, force)
|
scope.launch { mainViewModel.getCastAndCrew(itemId, type, force) }
|
||||||
mainViewModel.getWatchProviders(itemId, type, force)
|
scope.launch { mainViewModel.getSimilar(itemId, type) }
|
||||||
mainViewModel.getReviews(itemId, type, force)
|
scope.launch { mainViewModel.getVideos(itemId, type, force) }
|
||||||
|
scope.launch { mainViewModel.getWatchProviders(itemId, type, force) }
|
||||||
|
scope.launch { mainViewModel.getReviews(itemId, type, force) }
|
||||||
|
scope.launch { mainViewModel.getAccountStates(itemId, type) }
|
||||||
|
|
||||||
when (type) {
|
when (type) {
|
||||||
MediaViewType.MOVIE -> {
|
MediaViewType.MOVIE -> {
|
||||||
mainViewModel.getReleaseDates(itemId, force)
|
scope.launch { mainViewModel.getReleaseDates(itemId, force) }
|
||||||
}
|
}
|
||||||
MediaViewType.TV -> {
|
MediaViewType.TV -> {
|
||||||
mainViewModel.getContentRatings(itemId, force)
|
scope.launch { mainViewModel.getContentRatings(itemId, force) }
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@@ -159,8 +168,6 @@ fun MediaDetailScreen(
|
|||||||
type: MediaViewType,
|
type: MediaViewType,
|
||||||
windowSize: WindowSizeClass
|
windowSize: WindowSizeClass
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val mainViewModel = viewModel<MainViewModel>()
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
val applicationViewModel = viewModel<ApplicationViewModel>()
|
val applicationViewModel = viewModel<ApplicationViewModel>()
|
||||||
|
|
||||||
@@ -193,12 +200,11 @@ fun MediaDetailScreen(
|
|||||||
|
|
||||||
val isRefreshing = remember { mutableStateOf(false) }
|
val isRefreshing = remember { mutableStateOf(false) }
|
||||||
mainViewModel.monitorDetailsLoadingRefreshing(refreshing = isRefreshing)
|
mainViewModel.monitorDetailsLoadingRefreshing(refreshing = isRefreshing)
|
||||||
|
|
||||||
val pullRefreshState = rememberPullRefreshState(
|
val pullRefreshState = rememberPullRefreshState(
|
||||||
refreshing = isRefreshing.value,
|
refreshing = isRefreshing.value,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
scope.launch {
|
fetchData(mainViewModel, itemId, type, true)
|
||||||
fetchData(mainViewModel, itemId, type, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -232,7 +238,7 @@ fun MediaDetailScreen(
|
|||||||
windowSize = windowSize,
|
windowSize = windowSize,
|
||||||
showImageGallery = showGalleryOverlay,
|
showImageGallery = showGalleryOverlay,
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
mainViewModel = mainViewModel
|
mainViewModel = mainViewModel
|
||||||
)
|
)
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
refreshing = isRefreshing.value,
|
refreshing = isRefreshing.value,
|
||||||
@@ -254,9 +260,9 @@ fun MediaDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalPagerApi::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun MediaViewContent(
|
fun MediaViewContent(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
itemId: Int,
|
itemId: Int,
|
||||||
@@ -266,7 +272,7 @@ private fun MediaViewContent(
|
|||||||
windowSize: WindowSizeClass,
|
windowSize: WindowSizeClass,
|
||||||
showImageGallery: MutableState<Boolean>,
|
showImageGallery: MutableState<Boolean>,
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
preferences: AppPreferences = get(AppPreferences::class.java)
|
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -280,15 +286,22 @@ private fun MediaViewContent(
|
|||||||
.verticalScroll(state = rememberScrollState()),
|
.verticalScroll(state = rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
DetailHeader(
|
val detailsLoadingState = remember { mainViewModel.produceDetailsLoadingStateFor(type) }
|
||||||
posterUrl = TmdbUtils.getFullPosterPath(mediaItem?.posterPath),
|
val isLoading = detailsLoadingState.value.isIn(LoadingState.LOADING, LoadingState.REFRESHING)
|
||||||
posterContentDescription = mediaItem?.title,
|
|
||||||
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem?.backdropPath),
|
if (isLoading) {
|
||||||
rating = mediaItem?.voteAverage?.let { it / 10 },
|
PlaceholderDetailHeader()
|
||||||
imageCollection = images,
|
} else {
|
||||||
showGalleryOverlay = showImageGallery,
|
DetailHeader(
|
||||||
pagerState = pagerState
|
posterUrl = TmdbUtils.getFullPosterPath(mediaItem?.posterPath),
|
||||||
)
|
posterContentDescription = mediaItem?.title,
|
||||||
|
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem?.backdropPath),
|
||||||
|
rating = mediaItem?.voteAverage?.let { it / 10 },
|
||||||
|
imageCollection = images,
|
||||||
|
showGalleryOverlay = showImageGallery,
|
||||||
|
pagerState = pagerState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
@@ -300,13 +313,11 @@ private fun MediaViewContent(
|
|||||||
itemId = itemId
|
itemId = itemId
|
||||||
)
|
)
|
||||||
|
|
||||||
val externalIdsMap = remember { mainViewModel.produceExternalIdsFor(type) }
|
ExternalIdsArea(
|
||||||
externalIdsMap[itemId]?.let {
|
modifier = Modifier.padding(start = 20.dp),
|
||||||
ExternalIdsArea(
|
type = type,
|
||||||
externalIds = it,
|
itemId = itemId
|
||||||
modifier = Modifier.padding(start = 20.dp)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentSession = remember { SessionManager.currentSession }
|
val currentSession = remember { SessionManager.currentSession }
|
||||||
currentSession.value?.let {
|
currentSession.value?.let {
|
||||||
@@ -409,7 +420,8 @@ private fun MiscTvDetails(
|
|||||||
year = TmdbUtils.getSeriesRun(series),
|
year = TmdbUtils.getSeriesRun(series),
|
||||||
runtime = TmdbUtils.convertRuntimeToHoursMinutes(series),
|
runtime = TmdbUtils.convertRuntimeToHoursMinutes(series),
|
||||||
genres = series.genres,
|
genres = series.genres,
|
||||||
contentRating = contentRating
|
contentRating = contentRating,
|
||||||
|
type = MediaViewType.TV
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,23 +432,22 @@ private fun MiscMovieDetails(
|
|||||||
mediaItem: DetailedItem?,
|
mediaItem: DetailedItem?,
|
||||||
mainViewModel: MainViewModel
|
mainViewModel: MainViewModel
|
||||||
) {
|
) {
|
||||||
mediaItem?.let { mi ->
|
val movie = mediaItem as? DetailedMovie
|
||||||
val movie = mi as DetailedMovie
|
|
||||||
|
|
||||||
val contentRatingsMap = remember { mainViewModel.movieReleaseDates }
|
val contentRatingsMap = remember { mainViewModel.movieReleaseDates }
|
||||||
val contentRating = TmdbUtils.getMovieRating(contentRatingsMap[itemId])
|
val contentRating = TmdbUtils.getMovieRating(contentRatingsMap[itemId])
|
||||||
|
|
||||||
MiscDetails(
|
MiscDetails(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
year = movie.releaseDate?.getCalendarYear()?.toString() ?: "",
|
year = movie?.releaseDate?.getCalendarYear()?.toString() ?: "",
|
||||||
runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie),
|
runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie),
|
||||||
genres = movie.genres,
|
genres = movie?.genres ?: emptyList(),
|
||||||
contentRating = contentRating
|
contentRating = contentRating,
|
||||||
)
|
type = MediaViewType.MOVIE
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -445,8 +456,19 @@ private fun MiscDetails(
|
|||||||
year: String,
|
year: String,
|
||||||
runtime: String,
|
runtime: String,
|
||||||
genres: List<Genre>,
|
genres: List<Genre>,
|
||||||
contentRating: String
|
contentRating: String,
|
||||||
|
type: MediaViewType
|
||||||
) {
|
) {
|
||||||
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
|
val detailsLoadingState = remember { mainViewModel.produceDetailsLoadingStateFor(type) }
|
||||||
|
val isLoading = detailsLoadingState.value.isIn(LoadingState.LOADING, LoadingState.REFRESHING)
|
||||||
|
val shimmerModifier = if (isLoading) {
|
||||||
|
Modifier
|
||||||
|
.shimmerBackground(RoundedCornerShape(5.dp))
|
||||||
|
.width(40.dp)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
@@ -454,28 +476,35 @@ private fun MiscDetails(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(if (isLoading) 6.dp else 0.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = year, color = MaterialTheme.colorScheme.onBackground)
|
Text(
|
||||||
if (runtime != "0m") {
|
text = year.takeUnless { isLoading } ?: "",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = shimmerModifier
|
||||||
|
)
|
||||||
|
if (runtime != "0m" || isLoading) {
|
||||||
Text(
|
Text(
|
||||||
text = runtime,
|
text = runtime.takeUnless { isLoading } ?: "",
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
modifier = Modifier.padding(start = 12.dp)
|
modifier = shimmerModifier.padding(start = 12.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = contentRating,
|
text = contentRating.takeUnless { isLoading } ?: "",
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
modifier = Modifier.padding(start = 12.dp)
|
modifier = shimmerModifier.padding(start = 12.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val chips = if (isLoading) Genre.placeholderData else genres
|
||||||
ChipGroup(
|
ChipGroup(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
chips = genres.map { ChipInfo(it.name, false) }
|
chips = chips.map { ChipInfo(it.name, false) },
|
||||||
|
isLoading = isLoading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,62 +521,127 @@ private fun OverviewCard(
|
|||||||
val keywordsMap = remember { mainViewModel.produceKeywordsFor(type) }
|
val keywordsMap = remember { mainViewModel.produceKeywordsFor(type) }
|
||||||
val keywords = keywordsMap[itemId]
|
val keywords = keywordsMap[itemId]
|
||||||
|
|
||||||
mediaItem?.let { mi ->
|
val detailsLoadingState = remember { mainViewModel.produceAccountStatesLoadingStateFor(type) }
|
||||||
if (!mi.tagline.isNullOrEmpty() || keywords?.isNotEmpty() == true || !mi.overview.isNullOrEmpty()) {
|
val isLoading = detailsLoadingState.value.isIn(LoadingState.LOADING, LoadingState.REFRESHING)
|
||||||
ContentCard(
|
|
||||||
modifier = modifier
|
if (isLoading) {
|
||||||
|
ContentCard(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.width(150.dp)
|
||||||
|
.shimmerBackground(RoundedCornerShape(10.dp)),
|
||||||
|
text = "",
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(),
|
.height(100.dp)
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
.shimmerBackground(RoundedCornerShape(10.dp)),
|
||||||
|
text = "",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(ChipStyle.Rounded.mainAxisSpacing)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
mi.tagline?.let { tagline ->
|
ChipInfo.generatePlaceholders(4).forEach { keywordChipInfo ->
|
||||||
if (tagline.isNotEmpty()) {
|
RoundedChip(
|
||||||
Text(
|
modifier = Modifier.width(60.dp),
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
isLoading = true,
|
||||||
text = tagline,
|
chipInfo = keywordChipInfo,
|
||||||
color = MaterialTheme.colorScheme.tertiary
|
colors = ChipDefaults.roundedChipColors(
|
||||||
,
|
unselectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
unselectedContentColor = MaterialTheme.colorScheme.primary
|
||||||
fontStyle = FontStyle.Italic,
|
),
|
||||||
)
|
onSelectionChanged = {}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
Text(
|
}
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
|
||||||
text = mi.overview ?: "",
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
keywords?.let { keywords ->
|
}
|
||||||
val keywordsChipInfo = keywords.map { ChipInfo(it.name, true, it.id) }
|
}
|
||||||
Row(
|
} else {
|
||||||
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
mediaItem?.let { mi ->
|
||||||
horizontalArrangement = Arrangement.spacedBy(ChipStyle.Rounded.mainAxisSpacing)
|
if (!mi.tagline.isNullOrEmpty() || keywords?.isNotEmpty() == true || !mi.overview.isNullOrEmpty()) {
|
||||||
) {
|
ContentCard(
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
modifier = modifier
|
||||||
keywordsChipInfo.forEach { keywordChipInfo ->
|
) {
|
||||||
RoundedChip(
|
Column(
|
||||||
chipInfo = keywordChipInfo,
|
modifier = Modifier
|
||||||
colors = ChipDefaults.roundedChipColors(
|
.fillMaxWidth()
|
||||||
unselectedContainerColor = MaterialTheme.colorScheme.primary,
|
.wrapContentHeight(),
|
||||||
unselectedContentColor = MaterialTheme.colorScheme.primary
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
),
|
) {
|
||||||
onSelectionChanged = { chip ->
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
appNavController.navigate(AppNavItem.KeywordsView.withArgs(type, chip.text, chip.id!!))
|
mi.tagline?.let { tagline ->
|
||||||
}
|
if (tagline.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
text = tagline,
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
}
|
||||||
}
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
text = mi.overview ?: "",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
keywords?.let { keywords ->
|
||||||
|
val keywordsChipInfo = keywords.map { ChipInfo(it.name, true, it.id) }
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(ChipStyle.Rounded.mainAxisSpacing)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
keywordsChipInfo.forEach { keywordChipInfo ->
|
||||||
|
RoundedChip(
|
||||||
|
chipInfo = keywordChipInfo,
|
||||||
|
colors = ChipDefaults.roundedChipColors(
|
||||||
|
unselectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
unselectedContentColor = MaterialTheme.colorScheme.primary
|
||||||
|
),
|
||||||
|
onSelectionChanged = { chip ->
|
||||||
|
appNavController.navigate(
|
||||||
|
AppNavItem.KeywordsView.withArgs(
|
||||||
|
type,
|
||||||
|
chip.text,
|
||||||
|
chip.id!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -702,6 +796,9 @@ private fun CastCard(
|
|||||||
val castMap = remember { mainViewModel.produceCastFor(type) }
|
val castMap = remember { mainViewModel.produceCastFor(type) }
|
||||||
val cast = castMap[itemId]
|
val cast = castMap[itemId]
|
||||||
|
|
||||||
|
val loadingState = remember { mainViewModel.produceDetailsLoadingStateFor(type) }
|
||||||
|
val isLoading = loadingState.value.isIn(LoadingState.LOADING, LoadingState.REFRESHING)
|
||||||
|
|
||||||
ContentCard(
|
ContentCard(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = stringResource(R.string.cast_label),
|
title = stringResource(R.string.cast_label),
|
||||||
@@ -718,9 +815,15 @@ private fun CastCard(
|
|||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
items(cast?.size ?: 0) { i ->
|
if (isLoading) {
|
||||||
cast?.get(i)?.let {
|
items(5) {
|
||||||
CastCrewCard(appNavController = appNavController, person = it)
|
PlaceholderPosterItem()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items(cast?.size ?: 0) { i ->
|
||||||
|
cast?.get(i)?.let {
|
||||||
|
CastCrewCard(appNavController = appNavController, person = it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@@ -728,27 +831,40 @@ private fun CastCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
if (isLoading) {
|
||||||
text = stringResource(R.string.see_all_cast_and_crew),
|
Text(
|
||||||
fontSize = 12.sp,
|
text = "",
|
||||||
color = MaterialTheme.colorScheme.inversePrimary,
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.padding(start = 12.dp, bottom = 12.dp)
|
||||||
.padding(start = 12.dp, bottom = 12.dp)
|
.width(80.dp)
|
||||||
.clickable {
|
.shimmerBackground(RoundedCornerShape(10.dp))
|
||||||
appNavController.navigate(
|
)
|
||||||
AppNavItem.CastCrewListView.withArgs(
|
} else {
|
||||||
type,
|
Text(
|
||||||
itemId
|
text = stringResource(R.string.see_all_cast_and_crew),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.inversePrimary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 12.dp, bottom = 12.dp)
|
||||||
|
.clickable {
|
||||||
|
appNavController.navigate(
|
||||||
|
AppNavItem.CastCrewListView.withArgs(
|
||||||
|
type,
|
||||||
|
itemId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CastCrewCard(appNavController: NavController, person: Person) {
|
private fun CastCrewCard(
|
||||||
|
appNavController: NavController,
|
||||||
|
person: Person
|
||||||
|
) {
|
||||||
TwoLineImageTextCard(
|
TwoLineImageTextCard(
|
||||||
title = person.name,
|
title = person.name,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -952,7 +1068,7 @@ private fun VideoGroup(results: List<Video>, type: Video.Type, title: String) {
|
|||||||
|
|
||||||
val posterWidth = 120.dp
|
val posterWidth = 120.dp
|
||||||
LazyRow(modifier = Modifier
|
LazyRow(modifier = Modifier
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
@@ -1193,124 +1309,124 @@ private fun ReviewsCard(
|
|||||||
val reviewsMap = remember { mainViewModel.produceReviewsFor(type) }
|
val reviewsMap = remember { mainViewModel.produceReviewsFor(type) }
|
||||||
val reviews = reviewsMap[itemId]
|
val reviews = reviewsMap[itemId]
|
||||||
|
|
||||||
ListContentCard(
|
ListContentCard(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
header = {
|
header = {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(9.dp)
|
verticalArrangement = Arrangement.spacedBy(9.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.reviews_title),
|
text = stringResource(R.string.reviews_title),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
if (SessionManager.currentSession.value?.isAuthorized == true) {
|
if (SessionManager.currentSession.value?.isAuthorized == true) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp)
|
.height(50.dp)
|
||||||
.padding(bottom = 4.dp),
|
.padding(bottom = 4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
var reviewTextState by remember { mutableStateOf("") }
|
var reviewTextState by remember { mutableStateOf("") }
|
||||||
|
|
||||||
RoundedTextField(
|
RoundedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
value = reviewTextState,
|
value = reviewTextState,
|
||||||
onValueChange = { reviewTextState = it },
|
onValueChange = { reviewTextState = it },
|
||||||
placeHolder = stringResource(R.string.add_a_review_hint),
|
placeHolder = stringResource(R.string.add_a_review_hint),
|
||||||
backgroundColor = MaterialTheme.colorScheme.secondary,
|
backgroundColor = MaterialTheme.colorScheme.secondary,
|
||||||
placeHolderTextColor = MaterialTheme.colorScheme.background,
|
placeHolderTextColor = MaterialTheme.colorScheme.background,
|
||||||
textColor = MaterialTheme.colorScheme.onSecondary
|
textColor = MaterialTheme.colorScheme.onSecondary
|
||||||
)
|
)
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
CircleBackgroundColorImage(
|
CircleBackgroundColorImage(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
Toast
|
Toast
|
||||||
.makeText(context, "TODO", Toast.LENGTH_SHORT)
|
.makeText(context, "TODO", Toast.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
size = 40.dp,
|
size = 40.dp,
|
||||||
backgroundColor = MaterialTheme.colorScheme.tertiary,
|
backgroundColor = MaterialTheme.colorScheme.tertiary,
|
||||||
image = Icons.Filled.Send,
|
image = Icons.Filled.Send,
|
||||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.surfaceVariant),
|
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.surfaceVariant),
|
||||||
contentDescription = ""
|
contentDescription = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (reviews?.isNotEmpty() == true) {
|
if (reviews?.isNotEmpty() == true) {
|
||||||
reviews.reversed().forEachIndexed { index, review ->
|
reviews.reversed().forEachIndexed { index, review ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(end = 16.dp),
|
.padding(end = 16.dp),
|
||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
AvatarImage(
|
AvatarImage(
|
||||||
size = 50.dp,
|
size = 50.dp,
|
||||||
author = review.authorDetails
|
author = review.authorDetails
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = review.author,
|
text = review.author,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|
||||||
HtmlText(
|
HtmlText(
|
||||||
text = review.content,
|
text = review.content,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
val createdAt = TmdbUtils.formatDate(review.createdAt)
|
val createdAt = TmdbUtils.formatDate(review.createdAt)
|
||||||
val updatedAt = TmdbUtils.formatDate(review.updatedAt)
|
val updatedAt = TmdbUtils.formatDate(review.updatedAt)
|
||||||
var timestamp = stringResource(id = R.string.created_at_label, createdAt)
|
var timestamp = stringResource(id = R.string.created_at_label, createdAt)
|
||||||
if (updatedAt != createdAt) {
|
if (updatedAt != createdAt) {
|
||||||
timestamp += "\n${stringResource(id = R.string.updated_at_label, updatedAt)}"
|
timestamp += "\n${stringResource(id = R.string.updated_at_label, updatedAt)}"
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = timestamp,
|
text = timestamp,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
fontSize = 12.sp
|
fontSize = 12.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index != reviews.size - 1) {
|
if (index != reviews.size - 1) {
|
||||||
Divider(
|
Divider(
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
modifier = Modifier.padding(vertical = 12.dp)
|
modifier = Modifier.padding(vertical = 12.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(horizontal = 24.dp, vertical = 22.dp),
|
.padding(horizontal = 24.dp, vertical = 22.dp),
|
||||||
text = stringResource(R.string.no_reviews_label),
|
text = stringResource(R.string.no_reviews_label),
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.headlineMedium
|
style = MaterialTheme.typography.headlineMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1334,3 +1450,4 @@ fun DetailsFor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,14 +135,11 @@ fun PersonDetailScreen(
|
|||||||
elevation = 0.dp
|
elevation = 0.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
val externalIdsMap = remember { mainViewModel.peopleExternalIdsMap }
|
ExternalIdsArea(
|
||||||
val externalIds = externalIdsMap[personId]
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
externalIds?.let {
|
type = MediaViewType.PERSON,
|
||||||
ExternalIdsArea(
|
itemId = personId
|
||||||
externalIds = it,
|
)
|
||||||
modifier = Modifier.padding(start = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BiographyCard(person = person)
|
BiographyCard(person = person)
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,12 @@ class MainViewModel: ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun produceExternalIdsFor(type: MediaViewType): Map<Int, ExternalIds> {
|
fun produceExternalIdsFor(type: MediaViewType): Map<Int, ExternalIds> {
|
||||||
return providesForType(type, { movieExternalIds }, { tvExternalIds })
|
return when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieExternalIds
|
||||||
|
MediaViewType.TV -> tvExternalIds
|
||||||
|
MediaViewType.PERSON -> peopleExternalIdsMap
|
||||||
|
else -> throw ViewableMediaTypeException(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun produceKeywordsFor(type: MediaViewType): Map<Int, List<Keyword>> {
|
fun produceKeywordsFor(type: MediaViewType): Map<Int, List<Keyword>> {
|
||||||
@@ -263,6 +268,47 @@ class MainViewModel: ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun produceDetailsLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieDetailsLoadingState }, { tvDetailsLoadingState})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceImagesLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieImagesLoadingState }, { tvImagesLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceCastCrewLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieCastCrewLoadingState }, { tvCastCrewLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceVideosLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieVideosLoadingState }, { tvVideosLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceReviewsLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieReviewsLoadingState }, { tvReviewsLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceKeywordsLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieKeywordsLoadingState }, { tvKeywordsLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceWatchProvidersLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieWatchProvidersLoadingState }, { tvWatchProvidersLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceExternalIdsLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return when (mediaType) {
|
||||||
|
MediaViewType.MOVIE -> movieExternalIdsLoadingState
|
||||||
|
MediaViewType.TV -> tvExternalIdsLoadingState
|
||||||
|
MediaViewType.PERSON -> peopleExternalIdsLoadingState
|
||||||
|
else -> throw ViewableMediaTypeException(mediaType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceAccountStatesLoadingStateFor(mediaType: MediaViewType): MutableState<LoadingState> {
|
||||||
|
return providesForType(mediaType, { movieAccountStatesLoadingState }, { tvAccountStatesLoadingState })
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getById(id: Int, type: MediaViewType, force: Boolean = false) {
|
suspend fun getById(id: Int, type: MediaViewType, force: Boolean = false) {
|
||||||
when (type) {
|
when (type) {
|
||||||
MediaViewType.MOVIE -> if (detailMovies[id] == null || force) movieService.getById(id, force)
|
MediaViewType.MOVIE -> if (detailMovies[id] == null || force) movieService.getById(id, force)
|
||||||
@@ -453,4 +499,43 @@ class MainViewModel: ViewModel(), KoinComponent {
|
|||||||
peopleExternalIds.value == LoadingState.REFRESHING
|
peopleExternalIds.value == LoadingState.REFRESHING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ComposableNaming")
|
||||||
|
@Composable
|
||||||
|
fun monitorDetailsLoading(loading: 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 }
|
||||||
|
|
||||||
|
loading.value = listOf(
|
||||||
|
movieDetails.value, movieImages.value, movieCastCrew.value, movieVideos.value, movieReviews.value,
|
||||||
|
movieKeywords.value, movieWatchProviders.value, movieExternalIds.value, movieReleaseDates.value,
|
||||||
|
movieAccountStates.value, tvDetails.value, tvImages.value, tvCastCrew.value, tvVideos.value,
|
||||||
|
tvReviews.value, tvKeywords.value, tvWatchProviders.value, tvExternalIds.value, tvSeasons.value,
|
||||||
|
tvContentRatings.value, tvAccountStates.value, peopleDetails.value, peopleCastCrew.value,
|
||||||
|
peopleImages.value, peopleExternalIds.value
|
||||||
|
).any { it == LoadingState.LOADING || it == LoadingState.REFRESHING }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -104,8 +104,8 @@ object TmdbUtils {
|
|||||||
return "${startYear}-${endYear}"
|
return "${startYear}-${endYear}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertRuntimeToHoursMinutes(movie: DetailedMovie): String {
|
fun convertRuntimeToHoursMinutes(movie: DetailedMovie?): String {
|
||||||
movie.runtime?.let { runtime ->
|
movie?.runtime?.let { runtime ->
|
||||||
return convertRuntimeToHoursAndMinutes(runtime)
|
return convertRuntimeToHoursAndMinutes(runtime)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
Reference in New Issue
Block a user