refactor media detail view

This commit is contained in:
Owen LeJeune
2022-08-29 21:53:15 -04:00
parent c05a297327
commit 08ba3f1b08
3 changed files with 146 additions and 50 deletions

View File

@@ -272,10 +272,12 @@ fun MinLinesText(
)
}
class ChipInfo(val text: String, val enabled: Boolean = true)
sealed class ChipStyle(val mainAxisSpacing: Dp, val crossAxisSpacing: Dp) {
object Boxy: ChipStyle(8.dp, 4.dp)
object Rounded: ChipStyle(4.dp, 4.dp)
class Mixed(val predicate: (String) -> ChipStyle): ChipStyle(8.dp, 4.dp)
class Mixed(val predicate: (ChipInfo) -> ChipStyle): ChipStyle(8.dp, 4.dp)
}
@Composable
@@ -283,7 +285,8 @@ fun BoxyChip(
text: String,
style: TextStyle = MaterialTheme.typography.bodySmall,
isSelected: Boolean = true,
onSelectionChanged: (String) -> Unit = {}
onSelectionChanged: (String) -> Unit = {},
enabled: Boolean = true
) {
Surface(
// modifier = Modifier.padding(4.dp),
@@ -297,7 +300,8 @@ fun BoxyChip(
value = isSelected,
onValueChange = {
onSelectionChanged(text)
}
},
enabled = enabled
)
) {
Text(
@@ -315,7 +319,8 @@ fun RoundedChip(
text: String,
style: TextStyle = MaterialTheme.typography.bodySmall,
isSelected: Boolean = false,
onSelectionChanged: (String) -> Unit = {}
onSelectionChanged: (String) -> Unit = {},
enabled: Boolean = true
) {
val borderColor = if (isSelected) MaterialTheme.colorScheme.inverseSurface else MaterialTheme.colorScheme.onSurfaceVariant
val radius = style.fontSize.value.dp * 2
@@ -330,7 +335,8 @@ fun RoundedChip(
value = isSelected,
onValueChange = {
onSelectionChanged(text)
}
},
enabled = enabled
)
.padding(8.dp)
) {
@@ -346,24 +352,26 @@ fun RoundedChip(
@Composable
fun ChipGroup(
modifier: Modifier = Modifier,
chips: List<String> = emptyList(),
chips: List<ChipInfo> = emptyList(),
onSelectedChanged: (String) -> Unit = {},
chipStyle: ChipStyle = ChipStyle.Boxy
) {
@Composable
fun DrawChip(chipStyle: ChipStyle, chip: String) {
fun DrawChip(chipStyle: ChipStyle, chip: ChipInfo) {
when (chipStyle) {
ChipStyle.Boxy -> {
BoxyChip(
text = chip,
onSelectionChanged = onSelectedChanged
text = chip.text,
onSelectionChanged = onSelectedChanged,
enabled = chip.enabled
)
}
ChipStyle.Rounded -> {
RoundedChip(
text = chip,
onSelectionChanged = onSelectedChanged
text = chip.text,
onSelectionChanged = onSelectedChanged,
enabled = chip.enabled
)
}
is ChipStyle.Mixed -> {

View File

@@ -116,6 +116,60 @@ fun DetailHeader(
}
}
@Composable
fun DetailHeader2(
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 (
backdropImage, posterImage, 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)
},
url = posterUrl,
contentDescription = posterContentDescription,
elevation = 20.dp
)
rating?.let {
RatingView(
modifier = Modifier
.constrainAs(ratingsView) {
bottom.linkTo(parent.bottom)
start.linkTo(posterImage.end, margin = 20.dp)
},
progress = rating
)
}
}
}
@Composable
private fun Backdrop(modifier: Modifier, imageUrl: String?, contentDescription: String? = null) {
// val images = remember { mutableStateOf<ImageCollection?>(null) }
@@ -151,7 +205,7 @@ private fun TitleText(modifier: Modifier, title: String) {
}
@Composable
private fun RatingView(
fun RatingView(
progress: Float,
modifier: Modifier = Modifier
) {

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.CircleShape
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.*
@@ -18,6 +19,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -47,13 +49,14 @@ import kotlinx.coroutines.*
import org.json.JSONObject
import java.text.DecimalFormat
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MediaDetailView(
appNavController: NavController,
itemId: Int?,
type: MediaViewType
) {
val service = when(type) {
val service = when (type) {
MediaViewType.MOVIE -> MoviesService()
MediaViewType.TV -> TvService()
else -> throw IllegalArgumentException("Media type given: ${type}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
@@ -66,48 +69,79 @@ fun MediaDetailView(
}
}
Column(
modifier = Modifier
.background(color = MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.fillMaxSize()
.padding(bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
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 }
)
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
}
Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
// horizontalAlignment = Alignment.CenterHorizontally
) {
if (type == MediaViewType.MOVIE) {
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService)
} else {
MiscTvDetails(mediaItem = mediaItem, service as TvService)
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
SmallTopAppBar(
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults
.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.primary
),
title = { Text(text = mediaItem.value?.title ?: "") },
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)
) {
DetailHeader2(
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 }
)
ActionsView(itemId = itemId, type = type, service = service)
Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (type == MediaViewType.MOVIE) {
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService)
} else {
MiscTvDetails(mediaItem = mediaItem, service as TvService)
}
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
ActionsView(itemId = itemId, type = type, service = service)
CastCard(itemId = itemId, service = service, appNavController = appNavController)
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
CastCard(itemId = itemId, service = service, appNavController = appNavController)
VideosCard(itemId = itemId, service = service)
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
ReviewsCard(itemId = itemId, service = service)
}
VideosCard(itemId = itemId, service = service)
}
ReviewsCard(itemId = itemId, service = service)
}
}
}
}
}
@Composable
@@ -184,7 +218,7 @@ private fun MiscDetails(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
chips = genres.map { it.name }
chips = genres.map { ChipInfo(it.name, false) }
)
}
}
@@ -618,7 +652,7 @@ private fun OverviewCard(itemId: Int?, mediaItem: MutableState<DetailedItem?>, s
keywordResponse.value?.keywords?.let { keywords ->
val names = keywords.map { it.name }
val names = keywords.map { ChipInfo(it.name, false) }
ChipGroup(
chips = names,
chipStyle = ChipStyle.Rounded,
@@ -677,7 +711,7 @@ private fun CastCrewCard(appNavController: NavController, person: Person) {
imageUrl = TmdbUtils.getFullPersonImagePath(person),
noDataImage = R.drawable.no_person_photo,
titleTextColor = MaterialTheme.colorScheme.onPrimary,
subtitleTextColor = Color.Unspecified,
subtitleTextColor = MaterialTheme.colorScheme.onSecondary,
onItemClicked = {
appNavController.navigate(
"${MainNavItem.DetailView.route}/${MediaViewType.PERSON}/${person.id}"