redesign detail views titles

This commit is contained in:
Owen LeJeune
2023-07-16 17:27:03 -04:00
parent 7fb31cb041
commit 174b2eb7fa
4 changed files with 132 additions and 15 deletions

View File

@@ -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
}

View File

@@ -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
)
}

View File

@@ -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
)
}
}

View File

@@ -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,