mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 21:02:44 -05:00
refactor people view
This commit is contained in:
@@ -30,96 +30,8 @@ import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.RatingRing
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
|
||||
@Composable
|
||||
fun DetailContent(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Box(modifier = modifier
|
||||
.background(color = MaterialTheme.colorScheme.background)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DetailHeader(
|
||||
appNavController: NavController,
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
backdropUrl: String? = null,
|
||||
posterUrl: String? = null,
|
||||
backdropContentDescription: String? = null,
|
||||
posterContentDescription: String? = null,
|
||||
rating: Float? = null
|
||||
) {
|
||||
ConstraintLayout(modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
val (
|
||||
backButton, backdropImage, posterImage, titleText, ratingsView
|
||||
) = createRefs()
|
||||
|
||||
Backdrop(
|
||||
modifier = Modifier
|
||||
.constrainAs(backdropImage) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
imageUrl = backdropUrl,
|
||||
contentDescription = backdropContentDescription
|
||||
)
|
||||
|
||||
PosterItem(
|
||||
modifier = Modifier
|
||||
.constrainAs(posterImage) {
|
||||
bottom.linkTo(backdropImage.bottom)
|
||||
start.linkTo(parent.start, margin = 16.dp)
|
||||
top.linkTo(backButton.bottom)
|
||||
},
|
||||
url = posterUrl,
|
||||
contentDescription = posterContentDescription
|
||||
)
|
||||
|
||||
TitleText(
|
||||
modifier = Modifier
|
||||
.constrainAs(titleText) {
|
||||
bottom.linkTo(posterImage.bottom)
|
||||
start.linkTo(posterImage.end, margin = 8.dp)
|
||||
end.linkTo(parent.end, margin = 16.dp)
|
||||
},
|
||||
title = title
|
||||
)
|
||||
|
||||
rating?.let {
|
||||
RatingView(
|
||||
modifier = Modifier
|
||||
.constrainAs(ratingsView) {
|
||||
bottom.linkTo(titleText.top)
|
||||
start.linkTo(posterImage.end, margin = 20.dp)
|
||||
},
|
||||
progress = rating
|
||||
)
|
||||
}
|
||||
|
||||
BackButton(
|
||||
modifier = Modifier.constrainAs(backButton) {
|
||||
top.linkTo(parent.top)//, 8.dp)
|
||||
start.linkTo(parent.start, 8.dp)
|
||||
bottom.linkTo(posterImage.top)
|
||||
},
|
||||
appNavController = appNavController
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DetailHeader2(
|
||||
appNavController: NavController,
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
backdropUrl: String? = null,
|
||||
posterUrl: String? = null,
|
||||
@@ -188,22 +100,6 @@ private fun Backdrop(modifier: Modifier, imageUrl: String?, contentDescription:
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TitleText(modifier: Modifier, title: String) {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = modifier
|
||||
.padding(start = 16.dp, end = 16.dp)
|
||||
.fillMaxWidth(.6f),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
textAlign = TextAlign.Start,
|
||||
softWrap = true,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RatingView(
|
||||
progress: Float,
|
||||
@@ -225,23 +121,4 @@ fun RatingView(
|
||||
size = 50.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackButton(modifier: Modifier, appNavController: NavController) {
|
||||
val start = if (isSystemInDarkTheme()) Color.Black else Color.White
|
||||
IconButton(
|
||||
onClick = { appNavController.popBackStack() },
|
||||
modifier = modifier
|
||||
.background(
|
||||
brush = Brush.radialGradient(colors = listOf(start, Color.Transparent))
|
||||
)
|
||||
.wrapContentSize()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.content_description_back_button),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,9 +108,7 @@ fun MediaDetailView(
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
DetailHeader2(
|
||||
appNavController = appNavController,
|
||||
title = mediaItem.value?.title ?: "",
|
||||
DetailHeader(
|
||||
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
|
||||
posterContentDescription = mediaItem.value?.title,
|
||||
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import androidx.compose.animation.rememberSplineBasedDecay
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -30,6 +33,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PersonDetailView(
|
||||
appNavController: NavController,
|
||||
@@ -42,108 +46,131 @@ fun PersonDetailView(
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.background)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxSize()
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
DetailHeader(
|
||||
appNavController = appNavController,
|
||||
title = person.value?.name ?: "",
|
||||
posterUrl = TmdbUtils.getFullPersonImagePath(person.value?.profilePath),
|
||||
posterContentDescription = person.value?.name
|
||||
)
|
||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||
val scrollBehaviour = remember(decayAnimationSpec) {
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
|
||||
BiographyCard(person = person.value)
|
||||
|
||||
val credits = remember { mutableStateOf<PersonCreditsResponse?>(null) }
|
||||
personId?.let {
|
||||
if (credits.value == null) {
|
||||
fetchCredits(personId, credits)
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard(title = stringResource(R.string.known_for_label)) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
items(credits.value?.cast?.size ?: 0) { i ->
|
||||
val content = credits.value!!.cast[i]
|
||||
|
||||
TwoLineImageTextCard(
|
||||
title = content.name,
|
||||
titleTextColor = MaterialTheme.colorScheme.primary,
|
||||
subtitle = content.character,
|
||||
modifier = Modifier
|
||||
.width(124.dp)
|
||||
.wrapContentHeight(),
|
||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||
onItemClicked = {
|
||||
personId?.let {
|
||||
appNavController.navigate(
|
||||
"${MainNavItem.DetailView.route}/${content.mediaType}/${content.id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehaviour.nestedScrollConnection),
|
||||
topBar = {
|
||||
SmallTopAppBar(
|
||||
scrollBehavior = scrollBehaviour,
|
||||
colors = TopAppBarDefaults
|
||||
.largeTopAppBarColors(
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
title = { Text(text = person.value?.name ?: "") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { appNavController.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(id = R.string.content_description_back_button),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.background)
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
DetailHeader(
|
||||
posterUrl = TmdbUtils.getFullPersonImagePath(person.value?.profilePath),
|
||||
posterContentDescription = person.value?.profilePath
|
||||
)
|
||||
|
||||
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
|
||||
if (departments.isNotEmpty()) {
|
||||
ContentCard(title = stringResource(R.string.also_known_for_label)) {
|
||||
Column(
|
||||
BiographyCard(person = person.value)
|
||||
|
||||
val credits = remember { mutableStateOf<PersonCreditsResponse?>(null) }
|
||||
personId?.let {
|
||||
if (credits.value == null) {
|
||||
fetchCredits(personId, credits)
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard(title = stringResource(R.string.known_for_label)) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
departments.forEach { department ->
|
||||
Text(text = department, color = MaterialTheme.colorScheme.primary)
|
||||
LazyRow(
|
||||
items(credits.value?.cast?.size ?: 0) { i ->
|
||||
val content = credits.value!!.cast[i]
|
||||
|
||||
TwoLineImageTextCard(
|
||||
title = content.name,
|
||||
titleTextColor = MaterialTheme.colorScheme.primary,
|
||||
subtitle = content.character,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.width(124.dp)
|
||||
.wrapContentHeight(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
val jobsInDepartment =
|
||||
credits.value!!.crew.filter { it.department == department }
|
||||
items(jobsInDepartment.size) { i ->
|
||||
val content = jobsInDepartment[i]
|
||||
val title = if (content.mediaType == MediaViewType.MOVIE) {
|
||||
content.title ?: ""
|
||||
} else {
|
||||
content.name ?: ""
|
||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||
onItemClicked = {
|
||||
personId?.let {
|
||||
appNavController.navigate(
|
||||
"${MainNavItem.DetailView.route}/${content.mediaType}/${content.id}"
|
||||
)
|
||||
}
|
||||
TwoLineImageTextCard(
|
||||
title = title,
|
||||
subtitle = content.job,
|
||||
modifier = Modifier
|
||||
.width(124.dp)
|
||||
.wrapContentHeight(),
|
||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||
onItemClicked = {
|
||||
personId?.let {
|
||||
appNavController.navigate(
|
||||
"${MainNavItem.DetailView.route}/${content.mediaType}/${content.id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
|
||||
if (departments.isNotEmpty()) {
|
||||
ContentCard(title = stringResource(R.string.also_known_for_label)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
departments.forEach { department ->
|
||||
Text(text = department, color = MaterialTheme.colorScheme.primary)
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
val jobsInDepartment =
|
||||
credits.value!!.crew.filter { it.department == department }
|
||||
items(jobsInDepartment.size) { i ->
|
||||
val content = jobsInDepartment[i]
|
||||
val title = if (content.mediaType == MediaViewType.MOVIE) {
|
||||
content.title ?: ""
|
||||
} else {
|
||||
content.name ?: ""
|
||||
}
|
||||
)
|
||||
TwoLineImageTextCard(
|
||||
title = title,
|
||||
subtitle = content.job,
|
||||
modifier = Modifier
|
||||
.width(124.dp)
|
||||
.wrapContentHeight(),
|
||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||
onItemClicked = {
|
||||
personId?.let {
|
||||
appNavController.navigate(
|
||||
"${MainNavItem.DetailView.route}/${content.mediaType}/${content.id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user