refactor some detail view code

This commit is contained in:
Owen LeJeune
2022-03-03 17:20:14 -05:00
parent 2605f6b3f4
commit b8b5fd1e3b
7 changed files with 418 additions and 391 deletions

View File

@@ -7,12 +7,11 @@ class DetailCast(
@SerializedName("id") val id: Int,
@SerializedName("episode_count") val episodeCount: Int,
@SerializedName("overview") val overview: String,
@SerializedName("name") val name: String?,
@SerializedName("name", alternate = ["title"]) val name: String,
@SerializedName("media_type") val mediaType: MediaViewType,
@SerializedName("poster_path") val posterPath: String?,
@SerializedName("first_air_date") val firstAirDate: String,
@SerializedName("character") val character: String,
@SerializedName("title") val title: String?,
@SerializedName("adult") val isAdult: Boolean,
@SerializedName("release_date") val releaseDate: String
)

View File

@@ -156,7 +156,8 @@ fun PosterItem(
fun BackdropImage(
modifier: Modifier = Modifier,
imageUrl: String? = null,
collection: ImageCollection? = null
collection: ImageCollection? = null,
contentDescription: String? = null
) {
val context = LocalContext.current
@@ -200,7 +201,7 @@ fun BackdropImage(
} else {
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
},
contentDescription = "",
contentDescription = contentDescription,
modifier = Modifier.onGloballyPositioned {
sizeImage = it.size
}

View File

@@ -8,7 +8,7 @@ import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.owenlejeune.tvtime.ui.screens.DetailView
import com.owenlejeune.tvtime.ui.screens.MediaDetailView
import com.owenlejeune.tvtime.ui.screens.MainAppView
import com.owenlejeune.tvtime.ui.screens.MediaViewType
import com.owenlejeune.tvtime.ui.screens.PersonDetailView
@@ -40,7 +40,7 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar
val args = navBackStackEntry.arguments
val mediaType = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType
if (mediaType != MediaViewType.PERSON) {
DetailView(
MediaDetailView(
appNavController = navController,
itemId = args.getInt(NavConstants.ID_KEY),
type = mediaType

View File

@@ -0,0 +1,193 @@
package com.owenlejeune.tvtime.ui.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.navigation.NavController
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.components.BackdropImage
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)
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)
},
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)//, 12.dp)
bottom.linkTo(posterImage.top)
},
appNavController = appNavController
)
}
}
@Composable
private fun Backdrop(modifier: Modifier, imageUrl: String?, contentDescription: String? = null) {
// val images = remember { mutableStateOf<ImageCollection?>(null) }
// itemId?.let {
// if (images.value == null) {
// fetchImages(itemId, service, images)
// }
// }
BackdropImage(
modifier = modifier
.fillMaxWidth()
.height(280.dp),
imageUrl = TmdbUtils.getFullBackdropPath(imageUrl),
contentDescription = contentDescription
// collection = images.value
)
}
@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
private fun RatingView(
progress: Float,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.clip(CircleShape)
.size(60.dp)
.background(color = MaterialTheme.colorScheme.surfaceVariant)
) {
RatingRing(
modifier = Modifier.padding(5.dp),
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
progress = progress,
textSize = 14.sp,
ringColor = MaterialTheme.colorScheme.primary,
ringStrokeWidth = 4.dp,
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
)
}
}

View File

@@ -1,21 +1,17 @@
package com.owenlejeune.tvtime.ui.screens
import android.widget.Toast
import androidx.compose.foundation.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
@@ -23,15 +19,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.navigation.NavController
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.DetailService
import com.owenlejeune.tvtime.api.tmdb.MoviesService
import com.owenlejeune.tvtime.api.tmdb.PeopleService
import com.owenlejeune.tvtime.api.tmdb.TvService
import com.owenlejeune.tvtime.api.tmdb.model.*
import com.owenlejeune.tvtime.extensions.listItems
@@ -47,7 +40,7 @@ import org.json.JSONObject
import java.text.DecimalFormat
@Composable
fun DetailView(
fun MediaDetailView(
appNavController: NavController,
itemId: Int?,
type: MediaViewType
@@ -65,349 +58,25 @@ fun DetailView(
}
}
val scrollState = rememberScrollState()
ConstraintLayout(
DetailContent(
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
.verticalScroll(state = scrollState)
) {
val (
backButton, backdropImage, posterImage, titleText, contentColumn, ratingsView
) = createRefs()
Backdrop(
modifier = Modifier.constrainAs(backdropImage) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
},
mediaItem = mediaItem
)
PosterItem(
mediaItem = mediaItem.value,
modifier = Modifier
.constrainAs(posterImage) {
bottom.linkTo(backdropImage.bottom)
start.linkTo(parent.start, margin = 16.dp)
top.linkTo(backButton.bottom)
}
)
TitleText(
modifier = Modifier.constrainAs(titleText) {
bottom.linkTo(posterImage.bottom)
start.linkTo(posterImage.end, margin = 8.dp)
end.linkTo(parent.end, margin = 16.dp)
},
title = mediaItem.value?.title ?: "",
)
RatingView(
modifier = Modifier
.constrainAs(ratingsView) {
bottom.linkTo(titleText.top)
start.linkTo(posterImage.end, margin = 20.dp)
},
progress = mediaItem.value?.voteAverage?.let { it / 10 } ?: 0f
)
BackButton(
modifier = Modifier.constrainAs(backButton) {
top.linkTo(parent.top)//, 8.dp)
start.linkTo(parent.start, 12.dp)
bottom.linkTo(posterImage.top)
},
appNavController = appNavController
)
ContentColumn(
modifier = Modifier.constrainAs(contentColumn) {
top.linkTo(backdropImage.bottom)//, margin = 8.dp)
},
itemId = itemId,
mediaItem = mediaItem,
service = service,
mediaType = type,
appNavController = appNavController
)
}
}
@Composable
fun PersonDetailView(
appNavController: NavController,
personId: Int?
) {
val person = remember { mutableStateOf<DetailPerson?>(null) }
personId?.let {
if (person.value == null) {
fetchPerson(personId, person)
}
}
val scrollState = rememberScrollState()
ConstraintLayout(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
.verticalScroll(state = scrollState)
) {
val (
backButton, backdropImage, profileImage, nameText, contentColumn
) = createRefs()
BackdropImage(
modifier = Modifier
.fillMaxWidth()
.height(280.dp)
.constrainAs(backdropImage) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
PosterItem(
person = person.value,
modifier = Modifier
.constrainAs(profileImage) {
bottom.linkTo(backdropImage.bottom)
start.linkTo(parent.start, margin = 16.dp)
top.linkTo(backButton.bottom)
}
)
TitleText(
modifier = Modifier.constrainAs(nameText) {
bottom.linkTo(profileImage.bottom)
start.linkTo(profileImage.end, margin = 8.dp)
end.linkTo(parent.end, margin = 16.dp)
},
title = person.value?.name ?: ""
)
BackButton(
modifier = Modifier.constrainAs(backButton) {
top.linkTo(parent.top)
start.linkTo(parent.start, 12.dp)
bottom.linkTo(profileImage.top)
},
appNavController = appNavController
)
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.constrainAs(contentColumn) {
top.linkTo(backdropImage.bottom)//, margin = 8.dp)
},
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
ExpandableContentCard { isExpanded ->
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
text = person.value?.biography ?: "",
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (isExpanded) Int.MAX_VALUE else 3,
overflow = TextOverflow.Ellipsis
)
}
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]
val title = if (content.mediaType == MediaViewType.MOVIE) {
content.title ?: ""
} else {
content.name ?: ""
}
TwoLineImageTextCard(
title = title,
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}"
)
}
}
)
}
}
}
ContentCard(title = stringResource(R.string.also_known_for_label)) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
if (departments.isNotEmpty()) {
departments.forEach { department ->
Text(text = department, color = MaterialTheme.colorScheme.onSurface)
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}"
)
}
}
)
}
}
}
}
}
}
}
}
}
@Composable
private fun Backdrop(modifier: Modifier, mediaItem: MutableState<DetailedItem?>) {
// val images = remember { mutableStateOf<ImageCollection?>(null) }
// itemId?.let {
// if (images.value == null) {
// fetchImages(itemId, service, images)
// }
// }
BackdropImage(
modifier = modifier
.fillMaxWidth()
.height(280.dp),
imageUrl = TmdbUtils.getFullBackdropPath(mediaItem.value),
// collection = images.value
)
}
@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
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
)
}
}
@Composable
private fun RatingView(
progress: Float,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.clip(CircleShape)
.size(60.dp)
.background(color = MaterialTheme.colorScheme.surfaceVariant)
) {
RatingRing(
modifier = Modifier.padding(5.dp),
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
progress = progress,
textSize = 14.sp,
ringColor = MaterialTheme.colorScheme.primary,
ringStrokeWidth = 4.dp,
size = 50.dp
)
}
}
@Composable
private fun ContentColumn(
modifier: Modifier,
itemId: Int?,
mediaItem: MutableState<DetailedItem?>,
service: DetailService,
mediaType: MediaViewType,
appNavController: NavController
) {
Column(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (mediaType == MediaViewType.MOVIE) {
DetailHeader(
appNavController = appNavController,
title = mediaItem.value?.title ?: "",
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
posterContentDescription = mediaItem.value?.title,
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
rating = mediaItem.value?.voteAverage?.let { it / 10 }
)
if (type == MediaViewType.MOVIE) {
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService)
} else {
MiscTvDetails(mediaItem = mediaItem, service as TvService)
@@ -419,14 +88,15 @@ private fun ContentColumn(
CastCard(itemId = itemId, service = service, appNavController = appNavController)
SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController)
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
VideosCard(itemId = itemId, service = service)
ActionsView(itemId = itemId, type = mediaType, service = service)
ActionsView(itemId = itemId, type = type, service = service)
ReviewsCard(itemId = itemId, service = service)
}
}
}
@Composable
@@ -1100,28 +770,6 @@ private fun fetchVideos(id: Int, service: DetailService, videoResponse: MutableS
}
}
private fun fetchPerson(id: Int, person: MutableState<DetailPerson?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = PeopleService().getPerson(id)
if (result.isSuccessful) {
withContext(Dispatchers.Main) {
person.value = result.body()
}
}
}
}
private fun fetchCredits(id: Int, credits: MutableState<PersonCreditsResponse?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = PeopleService().getCredits(id)
if (result.isSuccessful) {
withContext(Dispatchers.Main) {
credits.value = result.body()
}
}
}
}
private fun fetchReviews(id: Int, service: DetailService, reviewResponse: MutableState<ReviewResponse?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = service.getReviews(id)

View File

@@ -0,0 +1,186 @@
package com.owenlejeune.tvtime.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.PeopleService
import com.owenlejeune.tvtime.api.tmdb.model.DetailPerson
import com.owenlejeune.tvtime.api.tmdb.model.PersonCreditsResponse
import com.owenlejeune.tvtime.ui.components.ContentCard
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun PersonDetailView(
appNavController: NavController,
personId: Int?
) {
val person = remember { mutableStateOf<DetailPerson?>(null) }
personId?.let {
if (person.value == null) {
fetchPerson(personId, person)
}
}
DetailContent(
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, end = 16.dp, 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
)
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}"
)
}
}
)
}
}
}
ContentCard(title = stringResource(R.string.also_known_for_label)) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
if (departments.isNotEmpty()) {
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}"
)
}
}
)
}
}
}
}
}
}
}
}
}
@Composable
private fun BiographyCard(person: DetailPerson?) {
ExpandableContentCard { isExpanded ->
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
text = person?.biography ?: "",
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (isExpanded) Int.MAX_VALUE else 3,
overflow = TextOverflow.Ellipsis
)
}
}
private fun fetchPerson(id: Int, person: MutableState<DetailPerson?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = PeopleService().getPerson(id)
if (result.isSuccessful) {
withContext(Dispatchers.Main) {
person.value = result.body()
}
}
}
}
private fun fetchCredits(id: Int, credits: MutableState<PersonCreditsResponse?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = PeopleService().getCredits(id)
if (result.isSuccessful) {
withContext(Dispatchers.Main) {
credits.value = result.body()
}
}
}
}

View File

@@ -83,7 +83,7 @@ fun TVTimeTheme(
}
val systemUiController = rememberSystemUiController()
systemUiController.setStatusBarColor(colorScheme.background, !isDarkTheme)
systemUiController.setSystemBarsColor(colorScheme.background, !isDarkTheme)
MaterialTheme(
colorScheme = colorScheme,