mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-23 12:10:59 -05:00
backdrop gallery overlay on tap
This commit is contained in:
@@ -1,7 +1,14 @@
|
|||||||
package com.owenlejeune.tvtime.extensions
|
package com.owenlejeune.tvtime.extensions
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
fun Float.dpToPx(context: Context): Float {
|
fun Float.dpToPx(context: Context): Float {
|
||||||
return this * context.resources.displayMetrics.density
|
return this * context.resources.displayMetrics.density
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Int.toDp(): Dp = (this / LocalContext.current.resources.displayMetrics.density).toInt().dp
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
package com.owenlejeune.tvtime.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ChevronLeft
|
||||||
|
import androidx.compose.material.icons.outlined.ChevronRight
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
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.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.TileMode
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
|
import com.google.accompanist.pager.PagerState
|
||||||
|
import com.owenlejeune.tvtime.extensions.toDp
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
|
@Composable
|
||||||
|
fun Gallery(
|
||||||
|
pagerState: PagerState,
|
||||||
|
models: List<Any?>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
contentDescriptions: List<String> = emptyList()
|
||||||
|
) {
|
||||||
|
HorizontalPager(
|
||||||
|
count = models.size,
|
||||||
|
state = pagerState,
|
||||||
|
modifier = modifier
|
||||||
|
) { page ->
|
||||||
|
AsyncImage(
|
||||||
|
model = models[page],
|
||||||
|
contentDescription = contentDescriptions[page],
|
||||||
|
contentScale = ContentScale.FillWidth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
|
@Composable
|
||||||
|
fun TapGallery(
|
||||||
|
pagerState: PagerState,
|
||||||
|
models: List<Any?>,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var showControls by remember { mutableStateOf(false) }
|
||||||
|
var lastTappedTime by remember { mutableStateOf(System.currentTimeMillis()) }
|
||||||
|
|
||||||
|
var job: Job? = null
|
||||||
|
LaunchedEffect(lastTappedTime) {
|
||||||
|
if (showControls) {
|
||||||
|
job = scope.launch {
|
||||||
|
delay(5.seconds)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showControls = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.clickable {
|
||||||
|
job?.cancel()
|
||||||
|
showControls = true
|
||||||
|
lastTappedTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val sizeImage = remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
HorizontalPager(
|
||||||
|
count = models.size,
|
||||||
|
state = pagerState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.onGloballyPositioned { sizeImage.value = it.size }
|
||||||
|
) { page ->
|
||||||
|
AsyncImage(
|
||||||
|
model = models[page],
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillWidth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showControls,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut()
|
||||||
|
) {
|
||||||
|
val gradient = Brush.horizontalGradient(
|
||||||
|
colors = listOf(Color.Black, Color.Transparent),
|
||||||
|
startX = 0f,
|
||||||
|
endX = (sizeImage.value.width/2).toFloat(),
|
||||||
|
tileMode = TileMode.Mirror
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(brush = gradient)
|
||||||
|
.size(
|
||||||
|
width = sizeImage.value.width.toDp(),
|
||||||
|
height = sizeImage.value.height.toDp()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.width(width = 48.dp)
|
||||||
|
.align(Alignment.CenterStart),
|
||||||
|
onClick = {}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ChevronLeft,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
tint = MaterialTheme.colorScheme.surface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.width(width = 48.dp)
|
||||||
|
.align(Alignment.CenterEnd),
|
||||||
|
onClick = {}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ChevronRight,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
tint = MaterialTheme.colorScheme.surface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Box(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .background(brush = leftGradient)
|
||||||
|
// .height(height = sizeImage.value.height.toDp())
|
||||||
|
// .width((sizeImage.value.width/2).toDp())
|
||||||
|
// .align(Alignment.CenterStart)
|
||||||
|
// .onGloballyPositioned { leftSizeImage.value = it.size }
|
||||||
|
// .clickable {
|
||||||
|
// val target =
|
||||||
|
// if (pagerState.currentPage == 0) models.size - 1 else pagerState.currentPage - 1
|
||||||
|
// scope.launch { pagerState.animateScrollToPage(target) }
|
||||||
|
// }
|
||||||
|
// ) {
|
||||||
|
// Icon(
|
||||||
|
// imageVector = Icons.Outlined.ChevronLeft,
|
||||||
|
// contentDescription = null,
|
||||||
|
// modifier = Modifier
|
||||||
|
// .size(48.dp)
|
||||||
|
// .padding(start = 24.dp)
|
||||||
|
// .align(Alignment.CenterStart),
|
||||||
|
// tint = MaterialTheme.colorScheme.surface
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// Box(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .background(brush = rightGradient)
|
||||||
|
// .height(height = sizeImage.value.height.toDp())
|
||||||
|
// .width((sizeImage.value.width/2).toDp())
|
||||||
|
// .align(Alignment.CenterEnd)
|
||||||
|
// .onGloballyPositioned { rightSizeImage.value = it.size }
|
||||||
|
// .clickable {
|
||||||
|
// val target =
|
||||||
|
// if (pagerState.currentPage == models.size - 1) 0 else pagerState.currentPage + 1
|
||||||
|
// scope.launch { pagerState.animateScrollToPage(target) }
|
||||||
|
// }
|
||||||
|
// ) {
|
||||||
|
// Icon(
|
||||||
|
// imageVector = Icons.Outlined.ChevronRight,
|
||||||
|
// contentDescription = null,
|
||||||
|
// modifier = Modifier
|
||||||
|
// .size(48.dp)
|
||||||
|
// .padding(end = 24.dp)
|
||||||
|
// .align(Alignment.CenterEnd),
|
||||||
|
// tint = MaterialTheme.colorScheme.surface
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnimatedVisibility(
|
||||||
|
// visible = showControls.value,
|
||||||
|
// enter = fadeIn(),
|
||||||
|
// exit = fadeOut()
|
||||||
|
// ) {
|
||||||
|
// val leftSizeImage = remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
// val leftGradient = Brush.horizontalGradient(
|
||||||
|
// colors = listOf(Color.Black, Color.Transparent),
|
||||||
|
// startX = 0f,
|
||||||
|
// endX = leftSizeImage.value.width.toFloat()
|
||||||
|
// )
|
||||||
|
// Box(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .background(brush = leftGradient)
|
||||||
|
// .fillMaxHeight()
|
||||||
|
// .width(100.dp)
|
||||||
|
// .align(Alignment.CenterStart)
|
||||||
|
// .onGloballyPositioned { leftSizeImage.value = it.size }
|
||||||
|
// .clickable {
|
||||||
|
// val target =
|
||||||
|
// if (pagerState.currentPage == 0) models.size - 1 else pagerState.currentPage + 1
|
||||||
|
// scope.launch { pagerState.animateScrollToPage(target) }
|
||||||
|
// }
|
||||||
|
// ) {
|
||||||
|
// Icon(
|
||||||
|
// imageVector = Icons.Outlined.ChevronLeft,
|
||||||
|
// contentDescription = null,
|
||||||
|
// modifier = Modifier.size(48.dp)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val rightSizeImage = remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
// val rightGradient = Brush.horizontalGradient(
|
||||||
|
// colors = listOf(Color.Black, Color.Transparent),
|
||||||
|
// startX = leftSizeImage.value.width.toFloat(),
|
||||||
|
// endX = 0f
|
||||||
|
// )
|
||||||
|
// Box(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .background(brush = rightGradient)
|
||||||
|
// .fillMaxHeight()
|
||||||
|
// .width(100.dp)
|
||||||
|
// .align(Alignment.CenterEnd)
|
||||||
|
// .onGloballyPositioned { rightSizeImage.value = it.size }
|
||||||
|
// .clickable {
|
||||||
|
// val target =
|
||||||
|
// if (pagerState.currentPage == models.size - 1) 0 else pagerState.currentPage - 1
|
||||||
|
// scope.launch { pagerState.animateScrollToPage(target) }
|
||||||
|
// }
|
||||||
|
// ) {
|
||||||
|
// Icon(
|
||||||
|
// imageVector = Icons.Outlined.ChevronRight,
|
||||||
|
// contentDescription = null,
|
||||||
|
// modifier = Modifier.size(48.dp)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.owenlejeune.tvtime.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||||
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ImageGalleryOverlay(
|
||||||
|
imageCollection: ImageCollection,
|
||||||
|
selectedImage: Int,
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = Color.Black.copy(alpha = 0.7f))
|
||||||
|
.clickable(onClick = onDismissRequest)
|
||||||
|
) {
|
||||||
|
val pagerState = rememberPagerState(initialPage = selectedImage)
|
||||||
|
TapGallery(
|
||||||
|
pagerState = pagerState,
|
||||||
|
models = imageCollection.backdrops.map { TmdbUtils.getFullBackdropPath(it) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.owenlejeune.tvtime.ui.screens.main
|
|||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -20,6 +21,7 @@ import coil.compose.AsyncImage
|
|||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
|
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.tmdb.api.v3.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||||
@@ -31,15 +33,18 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailHeader(
|
fun DetailHeader(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
showGalleryOverlay: MutableState<Boolean>? = null,
|
||||||
imageCollection: ImageCollection? = null,
|
imageCollection: ImageCollection? = null,
|
||||||
backdropUrl: String? = null,
|
backdropUrl: String? = null,
|
||||||
posterUrl: String? = null,
|
posterUrl: String? = null,
|
||||||
backdropContentDescription: String? = null,
|
backdropContentDescription: String? = null,
|
||||||
posterContentDescription: String? = null,
|
posterContentDescription: String? = null,
|
||||||
rating: Float? = null
|
rating: Float? = null,
|
||||||
|
pagerState: PagerState? = null
|
||||||
) {
|
) {
|
||||||
ConstraintLayout(modifier = modifier
|
ConstraintLayout(modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -56,8 +61,12 @@ fun DetailHeader(
|
|||||||
top.linkTo(parent.top)
|
top.linkTo(parent.top)
|
||||||
start.linkTo(parent.start)
|
start.linkTo(parent.start)
|
||||||
end.linkTo(parent.end)
|
end.linkTo(parent.end)
|
||||||
|
}
|
||||||
|
.clickable {
|
||||||
|
showGalleryOverlay?.value = true
|
||||||
},
|
},
|
||||||
imageCollection = imageCollection
|
imageCollection = imageCollection,
|
||||||
|
state = pagerState
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Backdrop(
|
Backdrop(
|
||||||
@@ -148,15 +157,17 @@ private fun Backdrop(
|
|||||||
|
|
||||||
@OptIn(ExperimentalPagerApi::class)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun BackdropGallery(
|
fun BackdropGallery(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
imageCollection: ImageCollection?
|
imageCollection: ImageCollection?,
|
||||||
|
delayMillis: Long = 5000,
|
||||||
|
state: PagerState? = null
|
||||||
) {
|
) {
|
||||||
BackdropContainer(
|
BackdropContainer(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) { sizeImage ->
|
) { sizeImage ->
|
||||||
if (imageCollection != null) {
|
if (imageCollection != null) {
|
||||||
val pagerState = rememberPagerState()
|
val pagerState = state ?: rememberPagerState(initialPage = 0)
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
count = imageCollection.backdrops.size,
|
count = imageCollection.backdrops.size,
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
@@ -173,10 +184,11 @@ private fun BackdropGallery(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fixes an issue where using pagerState.current page breaks paging animations
|
// fixes an issue where using pagerState.current page breaks paging animations
|
||||||
|
if (delayMillis > 0) {
|
||||||
var key by remember { mutableStateOf(false) }
|
var key by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(key1 = key) {
|
LaunchedEffect(key1 = key) {
|
||||||
launch {
|
launch {
|
||||||
delay(5000)
|
delay(delayMillis)
|
||||||
with(pagerState) {
|
with(pagerState) {
|
||||||
val target = if (currentPage < pageCount - 1) currentPage + 1 else 0
|
val target = if (currentPage < pageCount - 1) currentPage + 1 else 0
|
||||||
animateScrollToPage(target)
|
animateScrollToPage(target)
|
||||||
@@ -187,6 +199,7 @@ private fun BackdropGallery(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RatingView(
|
fun RatingView(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens.main
|
package com.owenlejeune.tvtime.ui.screens.main
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.media.MediaActionSound
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@@ -31,6 +32,9 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import com.google.accompanist.pager.PagerState
|
||||||
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.DetailService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.DetailService
|
||||||
@@ -51,16 +55,17 @@ import com.owenlejeune.tvtime.utils.TmdbUtils
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.koin.java.KoinJavaComponent
|
import org.koin.java.KoinJavaComponent
|
||||||
|
import org.koin.java.KoinJavaComponent.get
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MediaDetailView(
|
fun MediaDetailView(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
itemId: Int?,
|
itemId: Int?,
|
||||||
type: MediaViewType,
|
type: MediaViewType,
|
||||||
windowSize: WindowSizeClass,
|
windowSize: WindowSizeClass,
|
||||||
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
|
preferences: AppPreferences = get(AppPreferences::class.java)
|
||||||
) {
|
) {
|
||||||
val service = when (type) {
|
val service = when (type) {
|
||||||
MediaViewType.MOVIE -> MoviesService()
|
MediaViewType.MOVIE -> MoviesService()
|
||||||
@@ -75,12 +80,25 @@ fun MediaDetailView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val images = remember { mutableStateOf<ImageCollection?>(null) }
|
||||||
|
itemId?.let {
|
||||||
|
if (preferences.showBackdropGallery && images.value == null) {
|
||||||
|
fetchImages(itemId, service, images)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||||
val scrollBehavior = remember(decayAnimationSpec) {
|
val scrollBehavior = remember(decayAnimationSpec) {
|
||||||
TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
|
TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pagerState = rememberPagerState(initialPage = 0)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
val showGalleryOverlay = remember { mutableStateOf(false) }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -107,6 +125,45 @@ fun MediaDetailView(
|
|||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(modifier = Modifier.padding(innerPadding)) {
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
|
MediaViewContent(
|
||||||
|
appNavController = appNavController,
|
||||||
|
itemId = itemId,
|
||||||
|
mediaItem = mediaItem,
|
||||||
|
images = images,
|
||||||
|
service = service,
|
||||||
|
type = type,
|
||||||
|
windowSize = windowSize,
|
||||||
|
showImageGallery = showGalleryOverlay,
|
||||||
|
pagerState = pagerState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showGalleryOverlay.value) {
|
||||||
|
images.value?.let {
|
||||||
|
ImageGalleryOverlay(
|
||||||
|
imageCollection = it,
|
||||||
|
selectedImage = pagerState.currentPage,
|
||||||
|
onDismissRequest = { showGalleryOverlay.value = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun MediaViewContent(
|
||||||
|
appNavController: NavController,
|
||||||
|
itemId: Int?,
|
||||||
|
mediaItem: MutableState<DetailedItem?>,
|
||||||
|
images: MutableState<ImageCollection?>,
|
||||||
|
service: DetailService,
|
||||||
|
type: MediaViewType,
|
||||||
|
windowSize: WindowSizeClass,
|
||||||
|
showImageGallery: MutableState<Boolean>,
|
||||||
|
pagerState: PagerState
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(color = MaterialTheme.colorScheme.background),
|
.background(color = MaterialTheme.colorScheme.background),
|
||||||
@@ -119,19 +176,14 @@ fun MediaDetailView(
|
|||||||
.verticalScroll(state = rememberScrollState()),
|
.verticalScroll(state = rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
val images = remember { mutableStateOf<ImageCollection?>(null) }
|
|
||||||
itemId?.let {
|
|
||||||
if (preferences.showBackdropGallery && images.value == null) {
|
|
||||||
fetchImages(itemId, service, images)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DetailHeader(
|
DetailHeader(
|
||||||
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
|
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
|
||||||
posterContentDescription = mediaItem.value?.title,
|
posterContentDescription = mediaItem.value?.title,
|
||||||
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
|
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
|
||||||
rating = mediaItem.value?.voteAverage?.let { it / 10 },
|
rating = mediaItem.value?.voteAverage?.let { it / 10 },
|
||||||
imageCollection = images.value
|
imageCollection = images.value,
|
||||||
|
showGalleryOverlay = showImageGallery,
|
||||||
|
pagerState = pagerState
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -178,8 +230,6 @@ fun MediaDetailView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvService) {
|
private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvService) {
|
||||||
@@ -616,6 +666,7 @@ private fun OverviewCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mediaItem.value?.let { mi ->
|
mediaItem.value?.let { mi ->
|
||||||
|
if (!mi.tagline.isNullOrEmpty() || keywordResponse.value?.keywords?.isNotEmpty() == true || !mi.overview.isNullOrEmpty()) {
|
||||||
ContentCard(
|
ContentCard(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
@@ -670,6 +721,7 @@ private fun OverviewCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AdditionalDetailsCard(
|
private fun AdditionalDetailsCard(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||||
@@ -33,7 +34,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PersonDetailView(
|
fun PersonDetailView(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
|
|||||||
Reference in New Issue
Block a user