mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-16 08:40:53 -05:00
show keywords
This commit is contained in:
@@ -21,4 +21,6 @@ interface DetailService {
|
||||
|
||||
suspend fun deleteRating(id: Int): Response<RatingResponse>
|
||||
|
||||
suspend fun getKeywords(id: Int): Response<KeywordsResponse>
|
||||
|
||||
}
|
||||
@@ -39,6 +39,9 @@ interface MoviesApi {
|
||||
@GET("movie/{id}/reviews")
|
||||
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")
|
||||
suspend fun postMovieRatingAsGuest(
|
||||
@Path("id") id: Int,
|
||||
|
||||
@@ -71,4 +71,8 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {
|
||||
return movieService.getKeywords(id)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,9 @@ interface TvApi {
|
||||
@GET("tv/{id}/reviews")
|
||||
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")
|
||||
suspend fun postTvRatingAsGuest(
|
||||
@Path("id") id: Int,
|
||||
|
||||
@@ -71,4 +71,8 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {
|
||||
return service.getKeywords(id)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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>?
|
||||
)
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.painter.BrushPainter
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
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
|
||||
fun Chip(
|
||||
fun BoxyChip(
|
||||
text: String,
|
||||
style: TextStyle = MaterialTheme.typography.bodySmall,
|
||||
isSelected: Boolean = true,
|
||||
onSelectionChanged: (String) -> Unit = {}
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.padding(4.dp),
|
||||
// modifier = Modifier.padding(4.dp),
|
||||
shadowElevation = 8.dp,
|
||||
shape = RoundedCornerShape(5.dp),
|
||||
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
|
||||
fun ChipGroup(
|
||||
modifier: Modifier = Modifier,
|
||||
chips: List<String> = emptyList(),
|
||||
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(
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
crossAxisSpacing = 4.dp,
|
||||
mainAxisSpacing = chipStyle.mainAxisSpacing
|
||||
) {
|
||||
chips.forEach { chip ->
|
||||
Chip(
|
||||
text = chip,
|
||||
onSelectionChanged = onSelectedChanged
|
||||
)
|
||||
DrawChip(chipStyle = chipStyle, chip = chip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,7 +382,7 @@ fun ChipGroup(
|
||||
@Preview
|
||||
@Composable
|
||||
fun ChipPreview() {
|
||||
Chip("Test Chip")
|
||||
BoxyChip("Test Chip")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,7 +620,9 @@ fun CircleBackgroundColorImage(
|
||||
.background(color = backgroundColor)
|
||||
) {
|
||||
val mod = if (imageHeight != null) {
|
||||
Modifier.align(imageAlignment).height(height = imageHeight)
|
||||
Modifier
|
||||
.align(imageAlignment)
|
||||
.height(height = imageHeight)
|
||||
} else {
|
||||
Modifier.align(imageAlignment)
|
||||
}
|
||||
@@ -596,7 +660,9 @@ fun AvatarImage(
|
||||
.background(color = MaterialTheme.colorScheme.tertiary)
|
||||
) {
|
||||
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(),
|
||||
color = MaterialTheme.colorScheme.onTertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
||||
@@ -401,10 +401,8 @@ private fun ContentColumn(
|
||||
MiscTvDetails(mediaItem = mediaItem, service as TvService)
|
||||
}
|
||||
|
||||
ActionsView(itemId = itemId, type = mediaType, service = service)
|
||||
|
||||
if (mediaItem.value?.overview?.isNotEmpty() == true) {
|
||||
OverviewCard(mediaItem = mediaItem)
|
||||
OverviewCard(itemId = itemId!!, mediaItem.value!!.overview!!, service)
|
||||
}
|
||||
|
||||
CastCard(itemId = itemId, service = service, appNavController = appNavController)
|
||||
@@ -412,6 +410,8 @@ private fun ContentColumn(
|
||||
SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController)
|
||||
|
||||
VideosCard(itemId = itemId, service = service)
|
||||
|
||||
ActionsView(itemId = itemId, type = mediaType, service = service)
|
||||
|
||||
ReviewsCard(itemId = itemId, service = service)
|
||||
}
|
||||
@@ -653,19 +653,43 @@ private fun RatingDialog(showDialog: MutableState<Boolean>, onValueConfirmed: (F
|
||||
}
|
||||
|
||||
@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(
|
||||
modifier = modifier
|
||||
) {
|
||||
Text(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp),
|
||||
text = mediaItem.value?.overview ?: "",
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user