mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-12-30 19:31:19 -05:00
small fixes + new action buttons
This commit is contained in:
@@ -14,7 +14,7 @@ import kotlin.reflect.KClass
|
|||||||
val networkModule = module {
|
val networkModule = module {
|
||||||
single { if (BuildConfig.DEBUG) DebugHttpClient() else ProdHttpClient() }
|
single { if (BuildConfig.DEBUG) DebugHttpClient() else ProdHttpClient() }
|
||||||
single<Converter> { GsonConverter() }
|
single<Converter> { GsonConverter() }
|
||||||
single { (baseUrl: String) -> Client(baseUrl) }
|
factory { (baseUrl: String) -> Client(baseUrl) }
|
||||||
|
|
||||||
single<Map<Class<*>, JsonDeserializer<*>>> { mapOf(ListItem::class.java to ListItemDeserializer()) }
|
single<Map<Class<*>, JsonDeserializer<*>>> { mapOf(ListItem::class.java to ListItemDeserializer()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -23,6 +24,7 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
@@ -150,7 +152,6 @@ fun PosterItem(
|
|||||||
elevation: Dp = 8.dp,
|
elevation: Dp = 8.dp,
|
||||||
contentDescription: String?
|
contentDescription: String?
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
Card(
|
Card(
|
||||||
elevation = elevation,
|
elevation = elevation,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -161,24 +162,26 @@ fun PosterItem(
|
|||||||
AsyncImage(
|
AsyncImage(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = width, height = height)
|
.size(width = width, height = height)
|
||||||
.clip(RoundedCornerShape(5f.dpToPx(context)))
|
.clip(RoundedCornerShape(5.dp))
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
),
|
),
|
||||||
model = url,
|
model = url,
|
||||||
placeholder = painterResource(id = placeholder),
|
placeholder = rememberAsyncImagePainter(model = placeholder),
|
||||||
contentDescription = contentDescription
|
contentDescription = contentDescription,
|
||||||
|
contentScale = ContentScale.FillBounds
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Image(
|
Image(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = width, height = height)
|
.size(width = width, height = height)
|
||||||
.clip(RoundedCornerShape(5f.dpToPx(context)))
|
.clip(RoundedCornerShape(5.dp))
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
),
|
),
|
||||||
painter = painterResource(id = noDataImage),
|
painter = rememberAsyncImagePainter(model = noDataImage),
|
||||||
contentDescription = contentDescription
|
contentDescription = contentDescription,
|
||||||
|
contentScale = ContentScale.FillBounds
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +215,7 @@ fun BackdropImage(
|
|||||||
val backdrop = collection.backdrops[page]
|
val backdrop = collection.backdrops[page]
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = TmdbUtils.getFullBackdropPath(backdrop),
|
model = TmdbUtils.getFullBackdropPath(backdrop),
|
||||||
placeholder = painterResource(id = R.drawable.placeholder),
|
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
modifier = Modifier.onGloballyPositioned { sizeImage = it.size }
|
modifier = Modifier.onGloballyPositioned { sizeImage = it.size }
|
||||||
)
|
)
|
||||||
@@ -221,15 +224,17 @@ fun BackdropImage(
|
|||||||
if (imageUrl != null) {
|
if (imageUrl != null) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = imageUrl,
|
model = imageUrl,
|
||||||
placeholder = painterResource(id = R.drawable.placeholder),
|
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = Modifier.onGloballyPositioned { sizeImage = it.size }
|
modifier = Modifier.onGloballyPositioned { sizeImage = it.size },
|
||||||
|
contentScale = ContentScale.FillWidth
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.placeholder),
|
painter = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = Modifier.onGloballyPositioned { sizeImage = it.size }
|
modifier = Modifier.onGloballyPositioned { sizeImage = it.size },
|
||||||
|
contentScale = ContentScale.FillWidth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails
|
||||||
@@ -551,7 +552,7 @@ fun FullScreenThumbnailVideoPlayer(
|
|||||||
),
|
),
|
||||||
model = "https://img.youtube.com/vi/${key}/hqdefault.jpg",
|
model = "https://img.youtube.com/vi/${key}/hqdefault.jpg",
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
placeholder = painterResource(id = R.drawable.placeholder)
|
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showFullscreenView.value) {
|
if (showFullscreenView.value) {
|
||||||
@@ -900,3 +901,27 @@ fun LinkableText(
|
|||||||
style = style
|
style = style
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TimeoutSnackbar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
timeoutMillis: Long = 400,
|
||||||
|
onDismiss: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
var snackbarVisible by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
if (snackbarVisible) {
|
||||||
|
Snackbar(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Text(text = text)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(timeoutMillis)
|
||||||
|
snackbarVisible = false
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ sealed class AccountTabNavItem(stringRes: Int, route: String, val mediaType: Med
|
|||||||
object RatedTvEpisodes: AccountTabNavItem(R.string.nav_rated_episodes_title, "rated_episodes_route", MediaViewType.EPISODE, screenContent, { SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() }, RatedEpisode::class)
|
object RatedTvEpisodes: AccountTabNavItem(R.string.nav_rated_episodes_title, "rated_episodes_route", MediaViewType.EPISODE, screenContent, { SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() }, RatedEpisode::class)
|
||||||
// object Lists
|
// object Lists
|
||||||
object FavoriteMovies: AccountTabNavItem(R.string.nav_favorite_movies_title, "favorite_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.favoriteMovies ?: emptyList() }, FavoriteMovie::class)
|
object FavoriteMovies: AccountTabNavItem(R.string.nav_favorite_movies_title, "favorite_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.favoriteMovies ?: emptyList() }, FavoriteMovie::class)
|
||||||
object FavoriteTvShows: AccountTabNavItem(R.string.nav_favorite_tv_show_title, "favorite_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.favoriteMovies ?: emptyList() }, FavoriteTvSeries::class)
|
object FavoriteTvShows: AccountTabNavItem(R.string.nav_favorite_tv_show_title, "favorite_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.favoriteTvShows ?: emptyList() }, FavoriteTvSeries::class)
|
||||||
object MovieWatchlist: AccountTabNavItem(R.string.nav_movie_watchlist_title, "movie_watchlist_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.movieWatchlist ?: emptyList() }, WatchlistMovie::class)
|
object MovieWatchlist: AccountTabNavItem(R.string.nav_movie_watchlist_title, "movie_watchlist_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.movieWatchlist ?: emptyList() }, WatchlistMovie::class)
|
||||||
object TvWatchlist: AccountTabNavItem(R.string.nav_tv_watchlist_title, "tv_watchlist_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.tvWatchlist ?: emptyList() }, WatchlistTvSeries::class)
|
object TvWatchlist: AccountTabNavItem(R.string.nav_tv_watchlist_title, "tv_watchlist_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.tvWatchlist ?: emptyList() }, WatchlistTvSeries::class)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ private fun Backdrop(modifier: Modifier, imageUrl: String?, contentDescription:
|
|||||||
BackdropImage(
|
BackdropImage(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(280.dp),
|
.height(230.dp),
|
||||||
imageUrl = TmdbUtils.getFullBackdropPath(imageUrl),
|
imageUrl = TmdbUtils.getFullBackdropPath(imageUrl),
|
||||||
contentDescription = contentDescription
|
contentDescription = contentDescription
|
||||||
// collection = images.value
|
// collection = images.value
|
||||||
|
|||||||
@@ -2,9 +2,15 @@ package com.owenlejeune.tvtime.ui.screens
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.keyframes
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
@@ -13,6 +19,7 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
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.graphics.ColorFilter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -22,10 +29,12 @@ import androidx.compose.ui.text.font.FontStyle
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
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.DetailService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.DetailService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
||||||
@@ -33,14 +42,13 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
|||||||
import com.owenlejeune.tvtime.extensions.listItems
|
import com.owenlejeune.tvtime.extensions.listItems
|
||||||
import com.owenlejeune.tvtime.ui.components.*
|
import com.owenlejeune.tvtime.ui.components.*
|
||||||
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.RatingSelected
|
||||||
|
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
|
||||||
import com.owenlejeune.tvtime.ui.theme.actionButtonColor
|
import com.owenlejeune.tvtime.ui.theme.actionButtonColor
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
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.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
@@ -82,7 +90,8 @@ fun MediaDetailView(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
// horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if (type == MediaViewType.MOVIE) {
|
if (type == MediaViewType.MOVIE) {
|
||||||
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService)
|
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService)
|
||||||
@@ -90,6 +99,8 @@ fun MediaDetailView(
|
|||||||
MiscTvDetails(mediaItem = mediaItem, service as TvService)
|
MiscTvDetails(mediaItem = mediaItem, service as TvService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActionsView(itemId = itemId, type = type, service = service)
|
||||||
|
|
||||||
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
|
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
|
||||||
|
|
||||||
CastCard(itemId = itemId, service = service, appNavController = appNavController)
|
CastCard(itemId = itemId, service = service, appNavController = appNavController)
|
||||||
@@ -98,8 +109,6 @@ fun MediaDetailView(
|
|||||||
|
|
||||||
VideosCard(itemId = itemId, service = service)
|
VideosCard(itemId = itemId, service = service)
|
||||||
|
|
||||||
ActionsView(itemId = itemId, type = type, service = service)
|
|
||||||
|
|
||||||
ReviewsCard(itemId = itemId, service = service)
|
ReviewsCard(itemId = itemId, service = service)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +171,6 @@ private fun MiscDetails(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
) {
|
) {
|
||||||
Text(text = year, color = MaterialTheme.colorScheme.onBackground)
|
Text(text = year, color = MaterialTheme.colorScheme.onBackground)
|
||||||
Text(
|
Text(
|
||||||
@@ -197,26 +205,29 @@ private fun ActionsView(
|
|||||||
val session = SessionManager.currentSession
|
val session = SessionManager.currentSession
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth(),
|
.wrapContentSize(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
RateButton(
|
RateButton(
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
type = type,
|
type = type,
|
||||||
service = service
|
service = service
|
||||||
)
|
)
|
||||||
|
|
||||||
if (session?.isAuthorized == true) {
|
if (session?.isAuthorized == true) {
|
||||||
ActionButton(
|
val accountService = AccountService()
|
||||||
modifier = Modifier.weight(1f),
|
WatchlistButton(
|
||||||
text = stringResource(R.string.add_to_list_action_label),
|
itemId = itemId,
|
||||||
onClick = { /*TODO*/ }
|
type = type
|
||||||
)
|
)
|
||||||
ActionButton(
|
ListButton(
|
||||||
modifier = Modifier.weight(1f),
|
itemId = itemId,
|
||||||
text = stringResource(R.string.favourite_label),
|
type = type,
|
||||||
onClick = { /*TODO*/ }
|
service = accountService
|
||||||
|
)
|
||||||
|
FavoriteButton(
|
||||||
|
itemId = itemId,
|
||||||
|
type = type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,15 +235,17 @@ private fun ActionsView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ActionButton(modifier: Modifier, text: String, onClick: () -> Unit) {
|
private fun ActionButton(
|
||||||
Button(
|
iconRes: Int,
|
||||||
modifier = modifier,
|
contentDescription: String,
|
||||||
shape = RoundedCornerShape(10.dp),
|
isSelected: Boolean,
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiary),
|
filledIconColor: Color,
|
||||||
onClick = onClick
|
modifier: Modifier = Modifier,
|
||||||
) {
|
unfilledIconColor: Color = MaterialTheme.colorScheme.background,
|
||||||
Text(text = text)
|
backgroundColor: Color = MaterialTheme.colorScheme.actionButtonColor,
|
||||||
}
|
onClick: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
// TODO - refactor buttons here
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -255,70 +268,217 @@ private fun RateButton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val showSessionDialog = remember { mutableStateOf(false) }
|
||||||
val showRatingDialog = remember { mutableStateOf(false) }
|
val showRatingDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val bgColor = MaterialTheme.colorScheme.background
|
||||||
|
val filledColor = RatingSelected
|
||||||
|
val tintColor = remember { Animatable(if (itemIsRated.value) filledColor else bgColor) }
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.animateContentSize(tween(durationMillis = 300))
|
||||||
|
.clip(CircleShape)
|
||||||
|
.height(40.dp)
|
||||||
|
.requiredWidthIn(min = 40.dp)
|
||||||
|
.background(color = MaterialTheme.colorScheme.actionButtonColor)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
if (session == null) {
|
||||||
|
showSessionDialog.value = true
|
||||||
|
} else {
|
||||||
|
showRatingDialog.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
painter = painterResource(id = R.drawable.ic_rating_star),
|
||||||
|
contentDescription = "",
|
||||||
|
tint = tintColor.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {})
|
||||||
|
|
||||||
|
val userRating = session?.getRatingForId(itemId) ?: 0f
|
||||||
|
RatingDialog(showDialog = showRatingDialog, rating = userRating, onValueConfirmed = { rating ->
|
||||||
|
if (rating > 0f) {
|
||||||
|
postRating(context, rating, itemId, service, itemIsRated)
|
||||||
|
coroutineScope.launch {
|
||||||
|
tintColor.animateTo(targetValue = filledColor, animationSpec = tween(300))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deleteRating(context, itemId, service, itemIsRated)
|
||||||
|
coroutineScope.launch {
|
||||||
|
tintColor.animateTo(targetValue = bgColor, animationSpec = tween(300))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchlistButton(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val session = SessionManager.currentSession
|
||||||
|
|
||||||
|
val hasWatchlistedItem = remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (type == MediaViewType.MOVIE) {
|
||||||
|
session?.hasWatchlistedMovie(itemId) == true
|
||||||
|
} else {
|
||||||
|
session?.hasWatchlistedTvShow(itemId) == true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val showSessionDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val bgColor = MaterialTheme.colorScheme.background
|
||||||
|
val filledColor = WatchlistSelected
|
||||||
|
val tintColor = remember { Animatable(if (hasWatchlistedItem.value) filledColor else bgColor) }
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.animateContentSize(tween(durationMillis = 300))
|
||||||
|
.clip(CircleShape)
|
||||||
|
.height(40.dp)
|
||||||
|
.requiredWidthIn(min = 40.dp)
|
||||||
|
.background(color = MaterialTheme.colorScheme.actionButtonColor)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
if (session == null) {
|
||||||
|
showSessionDialog.value = true
|
||||||
|
} else {
|
||||||
|
addToWatchlist(context, itemId, type, hasWatchlistedItem) { added ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
tintColor.animateTo(
|
||||||
|
targetValue = if (added) filledColor else bgColor,
|
||||||
|
animationSpec = tween(300)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
painter = painterResource(id = R.drawable.ic_watchlist),
|
||||||
|
contentDescription = "",
|
||||||
|
tint = tintColor.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ListButton(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
service: AccountService
|
||||||
|
) {
|
||||||
|
val session = SessionManager.currentSession
|
||||||
|
|
||||||
|
// val hasListedItem
|
||||||
|
|
||||||
val showSessionDialog = remember { mutableStateOf(false) }
|
val showSessionDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
CircleBackgroundColorImage(
|
CircleBackgroundColorImage(
|
||||||
modifier = modifier.clickable(
|
modifier = modifier.clickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (SessionManager.currentSession != null) {
|
if (session == null) {
|
||||||
showRatingDialog.value = true
|
|
||||||
} else {
|
|
||||||
showSessionDialog.value = true
|
showSessionDialog.value = true
|
||||||
|
} else {
|
||||||
|
// add to watchlsit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
size = 40.dp,
|
size = 40.dp,
|
||||||
backgroundColor = MaterialTheme.colorScheme.actionButtonColor,
|
backgroundColor = MaterialTheme.colorScheme.actionButtonColor,
|
||||||
painter = painterResource(id = R.drawable.ic_rating_star),
|
painter = painterResource(id = R.drawable.ic_add_to_list),
|
||||||
colorFilter = ColorFilter.tint(color = if (itemIsRated.value) RatingSelected else MaterialTheme.colorScheme.background),
|
colorFilter = ColorFilter.tint(color = /*if (hasWatchlistedItem.value) WatchlistSelected else*/ MaterialTheme.colorScheme.background),
|
||||||
contentDescription = ""
|
contentDescription = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
RatingDialog(showDialog = showRatingDialog, onValueConfirmed = { rating ->
|
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {})
|
||||||
if (rating > 0f) {
|
|
||||||
postRating(context, rating, itemId, service, itemIsRated)
|
|
||||||
} else {
|
|
||||||
deleteRating(context, itemId, service, itemIsRated)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {
|
|
||||||
showRatingDialog.value = it
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postRating(context: Context, rating: Float, itemId: Int, service: DetailService, itemIsRated: MutableState<Boolean>) {
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
@Composable
|
||||||
val response = service.postRating(itemId, RatingBody(rating = rating))
|
fun FavoriteButton(
|
||||||
if (response.isSuccessful) {
|
itemId: Int,
|
||||||
SessionManager.currentSession?.refresh(changed = arrayOf(SessionManager.Session.Changed.RatedMovies, SessionManager.Session.Changed.RatedTv))
|
type: MediaViewType,
|
||||||
withContext(Dispatchers.Main) {
|
modifier: Modifier = Modifier
|
||||||
itemIsRated.value = true
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val session = SessionManager.currentSession
|
||||||
|
|
||||||
|
val hasFavorited = remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (type == MediaViewType.MOVIE) {
|
||||||
|
session?.hasFavoritedMovie(itemId) == true
|
||||||
|
} else {
|
||||||
|
session?.hasFavoritedTvShow(itemId) == true
|
||||||
}
|
}
|
||||||
} else {
|
)
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val errorObj = JSONObject(response.errorBody().toString())
|
|
||||||
Toast.makeText(context, "Error: ${errorObj.getString("status_message")}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteRating(context: Context, itemId: Int, service: DetailService, itemIsRated: MutableState<Boolean>) {
|
val showSessionDialog = remember { mutableStateOf(false) }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = service.deleteRating(itemId)
|
val bgColor = MaterialTheme.colorScheme.background
|
||||||
if (response.isSuccessful) {
|
val filledColor = FavoriteSelected
|
||||||
SessionManager.currentSession?.refresh(changed = arrayOf(SessionManager.Session.Changed.RatedMovies, SessionManager.Session.Changed.RatedTv))
|
val tintColor = remember { Animatable(if (hasFavorited.value) filledColor else bgColor) }
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
itemIsRated.value = false
|
val coroutineScope = rememberCoroutineScope()
|
||||||
}
|
|
||||||
} else {
|
Box(
|
||||||
withContext(Dispatchers.Main) {
|
modifier = modifier
|
||||||
val errorObj = JSONObject(response.errorBody().toString())
|
.animateContentSize(tween(durationMillis = 300))
|
||||||
Toast.makeText(context, "Error: ${errorObj.getString("status_message")}", Toast.LENGTH_SHORT).show()
|
.clip(CircleShape)
|
||||||
}
|
.height(40.dp)
|
||||||
}
|
.requiredWidthIn(min = 40.dp)
|
||||||
|
.background(color = MaterialTheme.colorScheme.actionButtonColor)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
if (session == null) {
|
||||||
|
showSessionDialog.value = true
|
||||||
|
} else {
|
||||||
|
addToFavorite(context, itemId, type, hasFavorited) { added ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
tintColor.animateTo(
|
||||||
|
targetValue = if (added) filledColor else bgColor,
|
||||||
|
animationSpec = tween(300)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
painter = painterResource(id = R.drawable.ic_favorite),
|
||||||
|
contentDescription = "",
|
||||||
|
tint = tintColor.value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,14 +533,14 @@ private fun CreateSessionDialog(showDialog: MutableState<Boolean>, onSessionRetu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RatingDialog(showDialog: MutableState<Boolean>, onValueConfirmed: (Float) -> Unit) {
|
private fun RatingDialog(showDialog: MutableState<Boolean>, rating: Float, onValueConfirmed: (Float) -> Unit) {
|
||||||
|
|
||||||
fun formatPosition(position: Float): String {
|
fun formatPosition(position: Float): String {
|
||||||
return DecimalFormat("#.#").format(position.toInt()*5/10f)
|
return DecimalFormat("#.#").format(position.toInt()*5/10f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showDialog.value) {
|
if (showDialog.value) {
|
||||||
var sliderPosition by remember { mutableStateOf(0f) }
|
var sliderPosition by remember { mutableStateOf(rating) }
|
||||||
val formatted = formatPosition(sliderPosition).toFloat()
|
val formatted = formatPosition(sliderPosition).toFloat()
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
modifier = Modifier.wrapContentHeight(),
|
modifier = Modifier.wrapContentHeight(),
|
||||||
@@ -885,3 +1045,97 @@ private fun fetchKeywords(id: Int, service: DetailService, keywordsResponse: Mut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun postRating(context: Context, rating: Float, itemId: Int, service: DetailService, itemIsRated: MutableState<Boolean>) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val response = service.postRating(itemId, RatingBody(rating = rating))
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
SessionManager.currentSession?.refresh(changed = arrayOf(SessionManager.Session.Changed.RatedMovies, SessionManager.Session.Changed.RatedTv))
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
itemIsRated.value = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val errorObj = JSONObject(response.errorBody().toString())
|
||||||
|
Toast.makeText(context, "Error: ${errorObj.getString("status_message")}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteRating(context: Context, itemId: Int, service: DetailService, itemIsRated: MutableState<Boolean>) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val response = service.deleteRating(itemId)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Rated)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
itemIsRated.value = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val errorObj = JSONObject(response.errorBody().toString())
|
||||||
|
Toast.makeText(context, "Error: ${errorObj.getString("status_message")}", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ActionSnackBar(
|
||||||
|
message: String
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,8 @@ private val LightColorPalette = lightColorScheme(
|
|||||||
fun TVTimeTheme(
|
fun TVTimeTheme(
|
||||||
isDarkTheme: Boolean = isSystemInDarkTheme(),
|
isDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
isDynamicColor: Boolean = true,
|
isDynamicColor: Boolean = true,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
||||||
dynamicColor && isDarkTheme -> {
|
dynamicColor && isDarkTheme -> {
|
||||||
|
|||||||
@@ -140,6 +140,13 @@ object SessionManager: KoinComponent {
|
|||||||
return ratedTvEpisodes.map { it.id }.contains(id)
|
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 hasFavoritedMovie(id: Int): Boolean {
|
fun hasFavoritedMovie(id: Int): Boolean {
|
||||||
return favoriteMovies.map { it.id }.contains(id)
|
return favoriteMovies.map { it.id }.contains(id)
|
||||||
}
|
}
|
||||||
@@ -173,6 +180,9 @@ object SessionManager: KoinComponent {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val All get() = values()
|
val All get() = values()
|
||||||
|
val Rated get() = arrayOf(RatedMovies, RatedTv, RatedEpisodes)
|
||||||
|
val Favorites get() = arrayOf(FavoriteMovies, FavoriteTv)
|
||||||
|
val Watchlist get() = arrayOf(WatchlistMovies, WatchlistTv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<corners android:radius="20dp" />
|
|
||||||
<solid android:color="@android:color/darker_gray" />
|
<solid android:color="@android:color/darker_gray" />
|
||||||
</shape>
|
</shape>
|
||||||
Reference in New Issue
Block a user