mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-17 01:00:55 -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.ui.components.RatingRing
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
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
|
@Composable
|
||||||
fun DetailHeader(
|
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,
|
modifier: Modifier = Modifier,
|
||||||
backdropUrl: String? = null,
|
backdropUrl: String? = null,
|
||||||
posterUrl: 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
|
@Composable
|
||||||
fun RatingView(
|
fun RatingView(
|
||||||
progress: Float,
|
progress: Float,
|
||||||
@@ -226,22 +122,3 @@ fun RatingView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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),
|
.padding(bottom = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
DetailHeader2(
|
DetailHeader(
|
||||||
appNavController = appNavController,
|
|
||||||
title = mediaItem.value?.title ?: "",
|
|
||||||
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),
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens.main
|
package com.owenlejeune.tvtime.ui.screens.main
|
||||||
|
|
||||||
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.stringResource
|
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
|
||||||
@@ -30,6 +33,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PersonDetailView(
|
fun PersonDetailView(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
@@ -42,108 +46,131 @@ fun PersonDetailView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
modifier = Modifier
|
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||||
.background(color = MaterialTheme.colorScheme.background)
|
val scrollBehaviour = remember(decayAnimationSpec) {
|
||||||
.verticalScroll(rememberScrollState())
|
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
|
||||||
.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
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
Scaffold(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.nestedScroll(scrollBehaviour.nestedScrollConnection),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
topBar = {
|
||||||
) {
|
SmallTopAppBar(
|
||||||
|
scrollBehavior = scrollBehaviour,
|
||||||
BiographyCard(person = person.value)
|
colors = TopAppBarDefaults
|
||||||
|
.largeTopAppBarColors(
|
||||||
val credits = remember { mutableStateOf<PersonCreditsResponse?>(null) }
|
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||||
personId?.let {
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
if (credits.value == null) {
|
),
|
||||||
fetchCredits(personId, credits)
|
title = { Text(text = person.value?.name ?: "") },
|
||||||
}
|
navigationIcon = {
|
||||||
}
|
IconButton(onClick = { appNavController.popBackStack() }) {
|
||||||
|
Icon(
|
||||||
ContentCard(title = stringResource(R.string.known_for_label)) {
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
LazyRow(
|
contentDescription = stringResource(id = R.string.content_description_back_button),
|
||||||
modifier = Modifier
|
tint = MaterialTheme.colorScheme.primary
|
||||||
.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}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
) { 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()
|
BiographyCard(person = person.value)
|
||||||
if (departments.isNotEmpty()) {
|
|
||||||
ContentCard(title = stringResource(R.string.also_known_for_label)) {
|
val credits = remember { mutableStateOf<PersonCreditsResponse?>(null) }
|
||||||
Column(
|
personId?.let {
|
||||||
|
if (credits.value == null) {
|
||||||
|
fetchCredits(personId, credits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentCard(title = stringResource(R.string.known_for_label)) {
|
||||||
|
LazyRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
departments.forEach { department ->
|
items(credits.value?.cast?.size ?: 0) { i ->
|
||||||
Text(text = department, color = MaterialTheme.colorScheme.primary)
|
val content = credits.value!!.cast[i]
|
||||||
LazyRow(
|
|
||||||
|
TwoLineImageTextCard(
|
||||||
|
title = content.name,
|
||||||
|
titleTextColor = MaterialTheme.colorScheme.primary,
|
||||||
|
subtitle = content.character,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.width(124.dp)
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||||
) {
|
onItemClicked = {
|
||||||
val jobsInDepartment =
|
personId?.let {
|
||||||
credits.value!!.crew.filter { it.department == department }
|
appNavController.navigate(
|
||||||
items(jobsInDepartment.size) { i ->
|
"${MainNavItem.DetailView.route}/${content.mediaType}/${content.id}"
|
||||||
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),
|
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
|
||||||
onItemClicked = {
|
if (departments.isNotEmpty()) {
|
||||||
personId?.let {
|
ContentCard(title = stringResource(R.string.also_known_for_label)) {
|
||||||
appNavController.navigate(
|
Column(
|
||||||
"${MainNavItem.DetailView.route}/${content.mediaType}/${content.id}"
|
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