mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 04:32:43 -05:00
redesign detail views titles
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.owenlejeune.tvtime.extensions
|
||||
|
||||
import android.view.View
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
@@ -7,7 +8,11 @@ import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@@ -16,6 +21,10 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.TileMode
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
fun Modifier.shimmerBackground(
|
||||
shape: Shape = RectangleShape,
|
||||
@@ -41,4 +50,40 @@ fun Modifier.shimmerBackground(
|
||||
tileMode = TileMode.Mirror,
|
||||
)
|
||||
return@composed this.then(background(brush, shape))
|
||||
}
|
||||
|
||||
fun Modifier.combinedOnVisibilityChange(
|
||||
onVisible: () -> Unit,
|
||||
onNotVisible: () -> Unit
|
||||
): Modifier = composed {
|
||||
val view = LocalView.current
|
||||
var isVisible by remember { mutableStateOf<Boolean?>(null) }
|
||||
|
||||
LaunchedEffect(isVisible) {
|
||||
when (isVisible) {
|
||||
true -> onVisible()
|
||||
false -> onNotVisible()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
onGloballyPositioned { coordinates ->
|
||||
isVisible = coordinates.isCompletelyVisible(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun LayoutCoordinates.isCompletelyVisible(view: View): Boolean {
|
||||
if (!isAttached) return false
|
||||
// Window relative bounds of our compose root view that are visible on the screen
|
||||
val globalRootRect = android.graphics.Rect()
|
||||
if (!view.getGlobalVisibleRect(globalRootRect)) {
|
||||
// we aren't visible at all.
|
||||
return false
|
||||
}
|
||||
val bounds = boundsInWindow()
|
||||
// Make sure we are completely in bounds.
|
||||
return bounds.top >= globalRootRect.top &&
|
||||
bounds.left >= globalRootRect.left &&
|
||||
bounds.right <= globalRootRect.right &&
|
||||
bounds.bottom <= globalRootRect.bottom
|
||||
}
|
||||
@@ -87,7 +87,7 @@ fun DetailHeader(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(start = 16.dp),
|
||||
.padding(start = 16.dp, top = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
@@ -119,7 +119,7 @@ private fun BackdropContainer(
|
||||
|
||||
val gradient = Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background),
|
||||
startY = sizeImage.value.height.toFloat() / 3,
|
||||
startY = 0f,//sizeImage.value.height.toFloat() / 4,
|
||||
endY = sizeImage.value.height.toFloat()
|
||||
)
|
||||
|
||||
@@ -130,8 +130,8 @@ private fun BackdropContainer(
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(sizeImage.value.width.toDp())
|
||||
.height(sizeImage.value.height.toDp())
|
||||
.width(sizeImage.value.width.toDp() + 2.dp)
|
||||
.height(sizeImage.value.height.toDp() + 4.dp)
|
||||
.background(gradient)
|
||||
)
|
||||
}
|
||||
@@ -187,7 +187,10 @@ fun BackdropGallery(
|
||||
HorizontalPager(
|
||||
count = imageCollection.backdrops.size,
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.onGloballyPositioned { sizeImage.value = it.size },
|
||||
) { page ->
|
||||
val backdrop = imageCollection.backdrops[page]
|
||||
val url = TmdbUtils.getFullBackdropPath(backdrop)
|
||||
@@ -201,7 +204,6 @@ fun BackdropGallery(
|
||||
model = model,
|
||||
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage.value = it.size },
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
@@ -87,6 +90,7 @@ 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.combinedOnVisibilityChange
|
||||
import com.owenlejeune.tvtime.extensions.format
|
||||
import com.owenlejeune.tvtime.extensions.getCalendarYear
|
||||
import com.owenlejeune.tvtime.extensions.isIn
|
||||
@@ -119,6 +123,7 @@ import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
||||
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.theme.Typography
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.SpecialFeaturesViewModel
|
||||
@@ -208,6 +213,7 @@ fun MediaDetailScreen(
|
||||
}
|
||||
)
|
||||
|
||||
val titleViewHidden = remember { mutableStateOf(false) }
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
@@ -217,7 +223,15 @@ fun MediaDetailScreen(
|
||||
topBar = {
|
||||
TVTTopAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
title = { Text(text = mediaItem?.title ?: "") },
|
||||
title = {
|
||||
AnimatedVisibility(
|
||||
visible = titleViewHidden.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
Text(text = mediaItem?.title ?: "")
|
||||
}
|
||||
},
|
||||
appNavController = appNavController,
|
||||
navigationIcon = {
|
||||
BackButton(navController = appNavController)
|
||||
@@ -238,7 +252,8 @@ fun MediaDetailScreen(
|
||||
windowSize = windowSize,
|
||||
showImageGallery = showGalleryOverlay,
|
||||
pagerState = pagerState,
|
||||
mainViewModel = mainViewModel
|
||||
mainViewModel = mainViewModel,
|
||||
titleViewHidden = titleViewHidden
|
||||
)
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing.value,
|
||||
@@ -272,6 +287,7 @@ fun MediaViewContent(
|
||||
windowSize: WindowSizeClass,
|
||||
showImageGallery: MutableState<Boolean>,
|
||||
pagerState: PagerState,
|
||||
titleViewHidden: MutableState<Boolean>,
|
||||
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
|
||||
) {
|
||||
Row(
|
||||
@@ -310,7 +326,8 @@ fun MediaViewContent(
|
||||
type = type,
|
||||
mediaItem = mediaItem,
|
||||
mainViewModel = mainViewModel,
|
||||
itemId = itemId
|
||||
itemId = itemId,
|
||||
titleViewHidden = titleViewHidden
|
||||
)
|
||||
|
||||
ExternalIdsArea(
|
||||
@@ -404,6 +421,7 @@ fun MediaViewContent(
|
||||
private fun MiscTvDetails(
|
||||
itemId: Int,
|
||||
mediaItem: DetailedItem?,
|
||||
titleViewHidden: MutableState<Boolean>,
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
mediaItem?.let { tv ->
|
||||
@@ -421,7 +439,9 @@ private fun MiscTvDetails(
|
||||
runtime = TmdbUtils.convertRuntimeToHoursMinutes(series),
|
||||
genres = series.genres,
|
||||
contentRating = contentRating,
|
||||
type = MediaViewType.TV
|
||||
type = MediaViewType.TV,
|
||||
title = mediaItem.title,
|
||||
titleViewHidden = titleViewHidden
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -430,6 +450,7 @@ private fun MiscTvDetails(
|
||||
private fun MiscMovieDetails(
|
||||
itemId: Int,
|
||||
mediaItem: DetailedItem?,
|
||||
titleViewHidden: MutableState<Boolean>,
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
val movie = mediaItem as? DetailedMovie
|
||||
@@ -446,18 +467,22 @@ private fun MiscMovieDetails(
|
||||
runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie),
|
||||
genres = movie?.genres ?: emptyList(),
|
||||
contentRating = contentRating,
|
||||
type = MediaViewType.MOVIE
|
||||
type = MediaViewType.MOVIE,
|
||||
title = mediaItem?.title ?: "",
|
||||
titleViewHidden = titleViewHidden
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MiscDetails(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
year: String,
|
||||
runtime: String,
|
||||
genres: List<Genre>,
|
||||
contentRating: String,
|
||||
type: MediaViewType
|
||||
type: MediaViewType,
|
||||
titleViewHidden: MutableState<Boolean>,
|
||||
) {
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
val detailsLoadingState = remember { mainViewModel.produceDetailsLoadingStateFor(type) }
|
||||
@@ -473,6 +498,20 @@ private fun MiscDetails(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title.takeUnless { isLoading } ?: "",
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = Typography.headlineLarge,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = shimmerModifier
|
||||
.width(400.dp)
|
||||
.combinedOnVisibilityChange(
|
||||
onVisible = { titleViewHidden.value = false },
|
||||
onNotVisible = { titleViewHidden.value = true }
|
||||
)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -1434,19 +1473,22 @@ fun DetailsFor(
|
||||
type: MediaViewType,
|
||||
itemId: Int,
|
||||
mediaItem: DetailedItem?,
|
||||
titleViewHidden: MutableState<Boolean>,
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
if (type == MediaViewType.MOVIE) {
|
||||
MiscMovieDetails(
|
||||
itemId = itemId,
|
||||
mediaItem = mediaItem,
|
||||
mainViewModel = mainViewModel
|
||||
mainViewModel = mainViewModel,
|
||||
titleViewHidden = titleViewHidden
|
||||
)
|
||||
} else {
|
||||
MiscTvDetails(
|
||||
itemId = itemId,
|
||||
mediaItem = mediaItem,
|
||||
mainViewModel = mainViewModel
|
||||
mainViewModel = mainViewModel,
|
||||
titleViewHidden = titleViewHidden
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
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.Arrangement
|
||||
@@ -43,6 +46,7 @@ import androidx.navigation.NavController
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||
import com.owenlejeune.tvtime.extensions.combinedOnVisibilityChange
|
||||
import com.owenlejeune.tvtime.ui.components.BackButton
|
||||
import com.owenlejeune.tvtime.ui.components.ContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||
@@ -52,6 +56,7 @@ import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.TVTTopAppBar
|
||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.theme.Typography
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
@@ -105,12 +110,21 @@ fun PersonDetailScreen(
|
||||
}
|
||||
)
|
||||
|
||||
val titleViewHidden = remember { mutableStateOf(false) }
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
TVTTopAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
title = { Text(text = person?.name ?: "") },
|
||||
title = {
|
||||
AnimatedVisibility(
|
||||
visible = titleViewHidden.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
Text(text = person?.name ?: "")
|
||||
}
|
||||
},
|
||||
appNavController = appNavController,
|
||||
navigationIcon = {
|
||||
BackButton(navController = appNavController)
|
||||
@@ -135,6 +149,20 @@ fun PersonDetailScreen(
|
||||
elevation = 0.dp
|
||||
)
|
||||
|
||||
Text(
|
||||
text = person?.name ?: "",
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = Typography.headlineLarge,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedOnVisibilityChange(
|
||||
onVisible = { titleViewHidden.value = false },
|
||||
onNotVisible = { titleViewHidden.value = true }
|
||||
)
|
||||
)
|
||||
|
||||
ExternalIdsArea(
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
type = MediaViewType.PERSON,
|
||||
|
||||
Reference in New Issue
Block a user