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.Context
import android.content.Intent import android.content.Intent
import android.widget.Toast
import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* 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.blur
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -34,6 +34,9 @@ import androidx.constraintlayout.compose.Dimension
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.owenlejeune.tvtime.R 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.ListV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.MediaList 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.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.navigation.MainNavItem 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 com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -190,14 +198,14 @@ private fun ListHeader(list: MediaList) {
Button( Button(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
onClick = { } onClick = { Toast.makeText(context, "Edit", Toast.LENGTH_SHORT).show() }
) { ) {
Text(text = stringResource(R.string.action_edit)) Text(text = stringResource(R.string.action_edit))
} }
Button( Button(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
onClick = { } onClick = { Toast.makeText(context, "Sort By", Toast.LENGTH_SHORT).show() }
) { ) {
Text(text = stringResource(R.string.action_sort_by)) Text(text = stringResource(R.string.action_sort_by))
} }
@@ -290,7 +298,7 @@ private fun ListItemView(
.padding(8.dp) .padding(8.dp)
.fillMaxSize() .fillMaxSize()
) { ) {
val (poster, content, delete) = createRefs() val (poster, content, ratingView) = createRefs()
AsyncImage( AsyncImage(
modifier = Modifier modifier = Modifier
@@ -310,7 +318,7 @@ private fun ListItemView(
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier modifier = Modifier
.constrainAs(content) { .constrainAs(content) {
end.linkTo(delete.start, margin = 12.dp) end.linkTo(ratingView.start, margin = 12.dp)
start.linkTo(poster.end, margin = 12.dp) start.linkTo(poster.end, margin = 12.dp)
width = Dimension.fillToConstraints width = Dimension.fillToConstraints
height = Dimension.matchParent height = Dimension.matchParent
@@ -323,29 +331,158 @@ private fun ListItemView(
Color.Black Color.Black
} }
Text( Text(
modifier = Modifier.padding(bottom = 8.dp),
text = listItem.title, text = listItem.title,
color = textColor, color = textColor,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
ActionButtonRow(listItem)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
} }
Image(
imageVector = Icons.Filled.Delete, val rating = SessionManager.currentSession?.getRatingForId(listItem.id, listItem.mediaType) ?: 0f
contentDescription = stringResource(R.string.remove_from_list_cd), RatingView(
colorFilter = ColorFilter.tint(color = Color.Red), progress = rating / 10f,
modifier = Modifier.constrainAs(delete) { modifier = Modifier
top.linkTo(parent.top) .constrainAs(ratingView) {
bottom.linkTo(parent.bottom) end.linkTo(parent.end)
end.linkTo(parent.end, margin = 12.dp) 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) { private fun shareListUrl(context: Context, listId: Int) {
val shareUrl = "https://www.themoviedb.org/list/$listId" val shareUrl = "https://www.themoviedb.org/list/$listId"
val sendIntent = Intent().apply { val sendIntent = Intent().apply {

View File

@@ -302,7 +302,7 @@ private fun ActionsView(
} }
@Composable @Composable
private fun ActionButton( fun ActionButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
itemId: Int, itemId: Int,
type: MediaViewType, type: MediaViewType,
@@ -412,7 +412,7 @@ private fun RateButton(
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {}) CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {})
val userRating = session?.getRatingForId(itemId) ?: 0f val userRating = session?.getRatingForId(itemId, type) ?: 0f
RatingDialog(showDialog = showRatingDialog, rating = userRating, onValueConfirmed = { rating -> RatingDialog(showDialog = showRatingDialog, rating = userRating, onValueConfirmed = { rating ->
if (rating > 0f) { if (rating > 0f) {
postRating(context, rating, itemId, service, itemIsRated) 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.AuthRequestBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -244,11 +245,13 @@ object SessionManager: KoinComponent {
return ratedTvEpisodes.map { it.id }.contains(id) return ratedTvEpisodes.map { it.id }.contains(id)
} }
fun getRatingForId(id: Int): Float { fun getRatingForId(id: Int, type: MediaViewType): Float? {
return ratedMovies.firstOrNull { it.id == id }?.rating return when(type) {
?: ratedTvShows.firstOrNull { it.id == id }?.rating MediaViewType.MOVIE -> ratedMovies.firstOrNull { it.id == id }?.rating
?: ratedTvEpisodes.firstOrNull { it.id == id }?.rating MediaViewType.TV -> ratedTvShows.firstOrNull { it.id == id }?.rating
?: 0f MediaViewType.EPISODE -> ratedTvEpisodes.firstOrNull { it.id == id }?.rating
else -> null
}
} }
fun hasFavoritedMovie(id: Int): Boolean { 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)"
}