show keywords

This commit is contained in:
Owen LeJeune
2022-02-28 22:51:20 -05:00
parent eebd5342ae
commit 3b7355892b
9 changed files with 152 additions and 19 deletions

View File

@@ -21,4 +21,6 @@ interface DetailService {
suspend fun deleteRating(id: Int): Response<RatingResponse> suspend fun deleteRating(id: Int): Response<RatingResponse>
suspend fun getKeywords(id: Int): Response<KeywordsResponse>
} }

View File

@@ -39,6 +39,9 @@ interface MoviesApi {
@GET("movie/{id}/reviews") @GET("movie/{id}/reviews")
suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse> suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse>
@GET("movie/{id}/keywords")
suspend fun getKeywords(@Path("id") id: Int): Response<KeywordsResponse>
@POST("movie/{id}/rating") @POST("movie/{id}/rating")
suspend fun postMovieRatingAsGuest( suspend fun postMovieRatingAsGuest(
@Path("id") id: Int, @Path("id") id: Int,

View File

@@ -71,4 +71,8 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
} }
} }
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {
return movieService.getKeywords(id)
}
} }

View File

@@ -39,6 +39,9 @@ interface TvApi {
@GET("tv/{id}/reviews") @GET("tv/{id}/reviews")
suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse> suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse>
@GET("tv/{id}/keywords")
suspend fun getKeywords(@Path("id") id: Int): Response<KeywordsResponse>
@POST("tv/{id}/rating") @POST("tv/{id}/rating")
suspend fun postTvRatingAsGuest( suspend fun postTvRatingAsGuest(
@Path("id") id: Int, @Path("id") id: Int,

View File

@@ -71,4 +71,8 @@ class TvService: KoinComponent, DetailService, HomePageService {
} }
} }
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {
return service.getKeywords(id)
}
} }

View File

@@ -0,0 +1,8 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
class Keyword(
@SerializedName("id") val id: Int,
@SerializedName("name") val name: String
)

View File

@@ -0,0 +1,8 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
class KeywordsResponse(
@SerializedName("id") val id: Int,
@SerializedName("keywords") val keywords: List<Keyword>?
)

View File

@@ -33,6 +33,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.painter.BrushPainter
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
@@ -267,15 +268,21 @@ fun MinLinesText(
) )
} }
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)
}
@Composable @Composable
fun Chip( fun BoxyChip(
text: String, text: String,
style: TextStyle = MaterialTheme.typography.bodySmall, style: TextStyle = MaterialTheme.typography.bodySmall,
isSelected: Boolean = true, isSelected: Boolean = true,
onSelectionChanged: (String) -> Unit = {} onSelectionChanged: (String) -> Unit = {}
) { ) {
Surface( Surface(
modifier = Modifier.padding(4.dp), // modifier = Modifier.padding(4.dp),
shadowElevation = 8.dp, shadowElevation = 8.dp,
shape = RoundedCornerShape(5.dp), shape = RoundedCornerShape(5.dp),
color = if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondary color = if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondary
@@ -299,20 +306,75 @@ fun Chip(
} }
} }
@Composable
fun RoundedChip(
text: String,
style: TextStyle = MaterialTheme.typography.bodySmall,
isSelected: Boolean = false,
onSelectionChanged: (String) -> Unit = {}
) {
val borderColor = if (isSelected) MaterialTheme.colorScheme.inverseSurface else MaterialTheme.colorScheme.onSurfaceVariant
val radius = style.fontSize.value.dp * 2
Surface(
border = BorderStroke(width = 1.dp, borderColor),
shape = RoundedCornerShape(radius),
color = MaterialTheme.colorScheme.surfaceVariant
) {
Row(
modifier = Modifier
.toggleable(
value = isSelected,
onValueChange = {
onSelectionChanged(text)
}
)
.padding(8.dp)
) {
Text(
text = text,
style = style,
color = if (isSelected) MaterialTheme.colorScheme.inverseSurface else MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
@Composable @Composable
fun ChipGroup( fun ChipGroup(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
chips: List<String> = emptyList(), chips: List<String> = emptyList(),
onSelectedChanged: (String) -> Unit = {}, onSelectedChanged: (String) -> Unit = {},
chipStyle: ChipStyle = ChipStyle.Boxy
) { ) {
@Composable
fun DrawChip(chipStyle: ChipStyle, chip: String) {
when (chipStyle) {
ChipStyle.Boxy -> {
BoxyChip(
text = chip,
onSelectionChanged = onSelectedChanged
)
}
ChipStyle.Rounded -> {
RoundedChip(
text = chip,
onSelectionChanged = onSelectedChanged
)
}
is ChipStyle.Mixed -> {
DrawChip(chipStyle = chipStyle.predicate(chip), chip = chip)
}
}
}
FlowRow( FlowRow(
modifier = modifier modifier = modifier,
crossAxisSpacing = 4.dp,
mainAxisSpacing = chipStyle.mainAxisSpacing
) { ) {
chips.forEach { chip -> chips.forEach { chip ->
Chip( DrawChip(chipStyle = chipStyle, chip = chip)
text = chip,
onSelectionChanged = onSelectedChanged
)
} }
} }
} }
@@ -320,7 +382,7 @@ fun ChipGroup(
@Preview @Preview
@Composable @Composable
fun ChipPreview() { fun ChipPreview() {
Chip("Test Chip") BoxyChip("Test Chip")
} }
/** /**
@@ -558,7 +620,9 @@ fun CircleBackgroundColorImage(
.background(color = backgroundColor) .background(color = backgroundColor)
) { ) {
val mod = if (imageHeight != null) { val mod = if (imageHeight != null) {
Modifier.align(imageAlignment).height(height = imageHeight) Modifier
.align(imageAlignment)
.height(height = imageHeight)
} else { } else {
Modifier.align(imageAlignment) Modifier.align(imageAlignment)
} }
@@ -596,7 +660,9 @@ fun AvatarImage(
.background(color = MaterialTheme.colorScheme.tertiary) .background(color = MaterialTheme.colorScheme.tertiary)
) { ) {
Text( Text(
modifier = Modifier.fillMaxSize().padding(top = size/5), modifier = Modifier
.fillMaxSize()
.padding(top = size / 5),
text = if (author.name.isNotEmpty()) author.name[0].uppercase() else author.username[0].toString(), text = if (author.name.isNotEmpty()) author.name[0].uppercase() else author.username[0].toString(),
color = MaterialTheme.colorScheme.onTertiary, color = MaterialTheme.colorScheme.onTertiary,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,

View File

@@ -401,10 +401,8 @@ private fun ContentColumn(
MiscTvDetails(mediaItem = mediaItem, service as TvService) MiscTvDetails(mediaItem = mediaItem, service as TvService)
} }
ActionsView(itemId = itemId, type = mediaType, service = service)
if (mediaItem.value?.overview?.isNotEmpty() == true) { if (mediaItem.value?.overview?.isNotEmpty() == true) {
OverviewCard(mediaItem = mediaItem) OverviewCard(itemId = itemId!!, mediaItem.value!!.overview!!, service)
} }
CastCard(itemId = itemId, service = service, appNavController = appNavController) CastCard(itemId = itemId, service = service, appNavController = appNavController)
@@ -412,6 +410,8 @@ private fun ContentColumn(
SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController) SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController)
VideosCard(itemId = itemId, service = service) VideosCard(itemId = itemId, service = service)
ActionsView(itemId = itemId, type = mediaType, service = service)
ReviewsCard(itemId = itemId, service = service) ReviewsCard(itemId = itemId, service = service)
} }
@@ -653,19 +653,43 @@ private fun RatingDialog(showDialog: MutableState<Boolean>, onValueConfirmed: (F
} }
@Composable @Composable
private fun OverviewCard(mediaItem: MutableState<DetailedItem?>, modifier: Modifier = Modifier) { private fun OverviewCard(itemId: Int, overview: String, service: DetailService, modifier: Modifier = Modifier) {
val keywordResponse = remember { mutableStateOf<KeywordsResponse?>(null) }
if (keywordResponse.value == null) {
fetchKeywords(itemId, service, keywordResponse)
}
val context = LocalContext.current
ContentCard( ContentCard(
modifier = modifier modifier = modifier
) { ) {
Text( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(vertical = 12.dp, horizontal = 16.dp), .padding(vertical = 12.dp, horizontal = 16.dp),
text = mediaItem.value?.overview ?: "", verticalArrangement = Arrangement.spacedBy(8.dp)
color = MaterialTheme.colorScheme.onSurfaceVariant, ) {
style = MaterialTheme.typography.bodyMedium Text(
) text = overview,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium
)
keywordResponse.value?.keywords?.let { keywords ->
val names = keywords.map { it.name }
ChipGroup(
chips = names,
chipStyle = ChipStyle.Rounded,
onSelectedChanged = { chip ->
if (service is MoviesService) {
// Toast.makeText(context, chip, Toast.LENGTH_SHORT).show()
}
}
)
}
}
} }
} }
@@ -1085,4 +1109,15 @@ private fun fetchReviews(id: Int, service: DetailService, reviewResponse: Mutabl
} }
} }
} }
}
private fun fetchKeywords(id: Int, service: DetailService, keywordsResponse: MutableState<KeywordsResponse?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = service.getKeywords(id)
if (result.isSuccessful) {
withContext(Dispatchers.Main) {
keywordsResponse.value = result.body()
}
}
}
} }