details for list items

This commit is contained in:
Owen LeJeune
2023-03-23 13:24:16 -04:00
parent 4e833269d0
commit a9fb0cc9ac
4 changed files with 179 additions and 21 deletions

View File

@@ -2,6 +2,7 @@ package com.owenlejeune.tvtime.ui.screens.main
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@@ -20,7 +21,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
@@ -34,6 +34,9 @@ import androidx.constraintlayout.compose.Dimension
import androidx.navigation.NavController
import coil.compose.AsyncImage
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MarkAsFavoriteBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.MediaList
@@ -41,6 +44,11 @@ import com.owenlejeune.tvtime.extensions.WindowSizeClass
import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
import com.owenlejeune.tvtime.ui.theme.RatingSelected
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
import com.owenlejeune.tvtime.ui.theme.actionButtonColor
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -190,14 +198,14 @@ private fun ListHeader(list: MediaList) {
Button(
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(10.dp),
onClick = { }
onClick = { Toast.makeText(context, "Edit", Toast.LENGTH_SHORT).show() }
) {
Text(text = stringResource(R.string.action_edit))
}
Button(
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(10.dp),
onClick = { }
onClick = { Toast.makeText(context, "Sort By", Toast.LENGTH_SHORT).show() }
) {
Text(text = stringResource(R.string.action_sort_by))
}
@@ -290,7 +298,7 @@ private fun ListItemView(
.padding(8.dp)
.fillMaxSize()
) {
val (poster, content, delete) = createRefs()
val (poster, content, ratingView) = createRefs()
AsyncImage(
modifier = Modifier
@@ -310,7 +318,7 @@ private fun ListItemView(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.constrainAs(content) {
end.linkTo(delete.start, margin = 12.dp)
end.linkTo(ratingView.start, margin = 12.dp)
start.linkTo(poster.end, margin = 12.dp)
width = Dimension.fillToConstraints
height = Dimension.matchParent
@@ -323,29 +331,158 @@ private fun ListItemView(
Color.Black
}
Text(
modifier = Modifier.padding(bottom = 8.dp),
text = listItem.title,
color = textColor,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
ActionButtonRow(listItem)
Spacer(modifier = Modifier.weight(1f))
}
Image(
imageVector = Icons.Filled.Delete,
contentDescription = stringResource(R.string.remove_from_list_cd),
colorFilter = ColorFilter.tint(color = Color.Red),
modifier = Modifier.constrainAs(delete) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(parent.end, margin = 12.dp)
}
val rating = SessionManager.currentSession?.getRatingForId(listItem.id, listItem.mediaType) ?: 0f
RatingView(
progress = rating / 10f,
modifier = Modifier
.constrainAs(ratingView) {
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}
)
}
}
}
}
@Composable
private fun DeleteButton(
modifier: Modifier
) {
Box(
modifier = modifier
.clip(CircleShape)
.size(48.dp)
.background(color = MaterialTheme.colorScheme.actionButtonColor)
.clickable(
onClick = {
}
)
) {
Icon(
modifier = Modifier
.clip(CircleShape)
.align(Alignment.Center),
imageVector = Icons.Filled.Delete,
contentDescription = stringResource(id = R.string.remove_from_list_cd),
tint = Color.Red
)
}
}
@Composable
private fun ActionButtonRow(listItem: ListItem) {
val session = SessionManager.currentSession
val (isFavourited, isWatchlisted, isRated) = if (listItem.mediaType == MediaViewType.MOVIE) {
Triple(
session?.hasFavoritedMovie(listItem.id) == true,
session?.hasWatchlistedMovie(listItem.id) == true,
session?.hasRatedMovie(listItem.id) == true
)
} else {
Triple(
session?.hasFavoritedTvShow(listItem.id) == true,
session?.hasWatchlistedTvShow(listItem.id) == true,
session?.hasRatedTvShow(listItem.id) == true
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
ActionButton(
itemId = listItem.id,
type = listItem.mediaType,
iconRes = R.drawable.ic_favorite,
contentDescription = stringResource(id = R.string.favourite_label),
isSelected = isFavourited,
filledIconColor = FavoriteSelected,
onClick = ::addToFavorite
)
ActionButton(
itemId = listItem.id,
type = listItem.mediaType,
iconRes = R.drawable.ic_watchlist,
contentDescription = "",
isSelected = isWatchlisted,
filledIconColor = WatchlistSelected,
onClick = ::addToWatchlist
)
val context = LocalContext.current
ActionButton(
itemId = listItem.id,
type = listItem.mediaType,
iconRes = R.drawable.ic_rating_star,
contentDescription = "",
isSelected = isRated,
filledIconColor = RatingSelected,
onClick = { c, i, t, s, f ->
// todo - add rating
Toast.makeText(context, "Rating", Toast.LENGTH_SHORT).show()
}
)
}
}
private fun addToWatchlist(
context: Context,
itemId: Int,
type: MediaViewType,
itemIsWatchlisted: MutableState<Boolean>,
onWatchlistChanged: (Boolean) -> Unit
) {
val accountId = SessionManager.currentSession!!.accountDetails!!.id
CoroutineScope(Dispatchers.IO).launch {
val response = AccountService().addToWatchlist(accountId, WatchlistBody(type, itemId, !itemIsWatchlisted.value))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Watchlist)
withContext(Dispatchers.Main) {
itemIsWatchlisted.value = !itemIsWatchlisted.value
onWatchlistChanged(itemIsWatchlisted.value)
}
} else {
withContext(Dispatchers.Main) {
Toast.makeText(context, "An error occurred", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun addToFavorite(
context: Context,
itemId: Int,
type: MediaViewType,
itemIsFavorited: MutableState<Boolean>,
onFavoriteChanged: (Boolean) -> Unit
) {
val accountId = SessionManager.currentSession!!.accountDetails!!.id
CoroutineScope(Dispatchers.IO).launch {
val response = AccountService().markAsFavorite(accountId, MarkAsFavoriteBody(type, itemId, !itemIsFavorited.value))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Favorites)
withContext(Dispatchers.Main) {
itemIsFavorited.value = !itemIsFavorited.value
onFavoriteChanged(itemIsFavorited.value)
}
}
}
}
private fun shareListUrl(context: Context, listId: Int) {
val shareUrl = "https://www.themoviedb.org/list/$listId"
val sendIntent = Intent().apply {

View File

@@ -302,7 +302,7 @@ private fun ActionsView(
}
@Composable
private fun ActionButton(
fun ActionButton(
modifier: Modifier = Modifier,
itemId: Int,
type: MediaViewType,
@@ -412,7 +412,7 @@ private fun RateButton(
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {})
val userRating = session?.getRatingForId(itemId) ?: 0f
val userRating = session?.getRatingForId(itemId, type) ?: 0f
RatingDialog(showDialog = showRatingDialog, rating = userRating, onValueConfirmed = { rating ->
if (rating > 0f) {
postRating(context, rating, itemId, service, itemIsRated)

View File

@@ -21,6 +21,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -244,11 +245,13 @@ object SessionManager: KoinComponent {
return ratedTvEpisodes.map { it.id }.contains(id)
}
fun getRatingForId(id: Int): Float {
return ratedMovies.firstOrNull { it.id == id }?.rating
?: ratedTvShows.firstOrNull { it.id == id }?.rating
?: ratedTvEpisodes.firstOrNull { it.id == id }?.rating
?: 0f
fun getRatingForId(id: Int, type: MediaViewType): Float? {
return when(type) {
MediaViewType.MOVIE -> ratedMovies.firstOrNull { it.id == id }?.rating
MediaViewType.TV -> ratedTvShows.firstOrNull { it.id == id }?.rating
MediaViewType.EPISODE -> ratedTvEpisodes.firstOrNull { it.id == id }?.rating
else -> null
}
}
fun hasFavoritedMovie(id: Int): Boolean {

View File

@@ -0,0 +1,18 @@
package com.owenlejeune.tvtime.utils.types
import java.io.Serializable
/**
* Data class like Kotlin [Pair] but supporting three data items
*/
data class Triple<out A, out B, out C>(
val first: A,
val second: B,
val third: C
) : Serializable {
/**
* Returns string representation of the [Triple] including its [first], [second], and [third] values.
*/
override fun toString(): String = "($first, $second, $third)"
}