mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-17 01:00:55 -05:00
show keywords
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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")
|
@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,
|
||||||
|
|||||||
@@ -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.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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user