mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-24 04:30:54 -05:00
refactor support for paging backdrop images
This commit is contained in:
@@ -77,15 +77,20 @@ class TmdbClient: KoinComponent {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val apiParam = QueryParam("api_key", BuildConfig.TMDB_ApiKey)
|
||||
|
||||
val locale = Locale.current
|
||||
val languageCode = "${locale.language}-${locale.region}"
|
||||
val languageParam = QueryParam("language", languageCode)
|
||||
|
||||
val segments = chain.request().url.encodedPathSegments
|
||||
val sessionIdParam: QueryParam? = sessionIdParam(segments)
|
||||
|
||||
val builder = chain.request().url.newBuilder()
|
||||
builder.addQueryParams(apiParam, languageParam, sessionIdParam)
|
||||
builder.addQueryParams(apiParam, sessionIdParam)
|
||||
|
||||
if (shouldIncludeLanguageParam(segments)) {
|
||||
val locale = Locale.current
|
||||
val languageCode = "${locale.language}-${locale.region}"
|
||||
val languageParam = QueryParam("language", languageCode)
|
||||
|
||||
builder.addQueryParams(languageParam)
|
||||
}
|
||||
|
||||
val requestBuilder = chain.request().newBuilder().url(builder.build())
|
||||
|
||||
val request = requestBuilder.build()
|
||||
@@ -106,6 +111,16 @@ class TmdbClient: KoinComponent {
|
||||
}
|
||||
return sessionIdParam
|
||||
}
|
||||
|
||||
private fun shouldIncludeLanguageParam(urlSegments: List<String>): Boolean {
|
||||
val ignoredRoutes = listOf("images")
|
||||
for (route in ignoredRoutes) {
|
||||
if (urlSegments.contains(route)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private inner class V4Interceptor: Interceptor {
|
||||
|
||||
@@ -4,25 +4,6 @@ import com.owenlejeune.tvtime.api.QueryParam
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
|
||||
//
|
||||
//fun Interceptor.Chain.addQueryParams(vararg queryParams: QueryParam?): Request {
|
||||
// val original = request()
|
||||
// val originalHttpUrl = original.url
|
||||
//
|
||||
// val urlBuilder = originalHttpUrl.newBuilder()
|
||||
// queryParams.forEach { param ->
|
||||
// if (param != null) {
|
||||
// urlBuilder.addQueryParameter(param.key, param.param)
|
||||
// }
|
||||
// }
|
||||
// val url = urlBuilder.build()
|
||||
//
|
||||
// val requestBuilder = original.newBuilder()
|
||||
// .url(url)
|
||||
//
|
||||
// return requestBuilder.build()
|
||||
//}
|
||||
|
||||
fun HttpUrl.Builder.addQueryParams(vararg queryParams: QueryParam?): HttpUrl.Builder {
|
||||
return apply {
|
||||
queryParams.forEach { param ->
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.gson.Gson
|
||||
import com.kieronquinn.monetcompat.core.MonetCompat
|
||||
import com.owenlejeune.tvtime.BuildConfig
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import java.util.function.BiPredicate
|
||||
|
||||
class AppPreferences(context: Context) {
|
||||
|
||||
@@ -24,6 +25,7 @@ class AppPreferences(context: Context) {
|
||||
private val USE_SYSTEM_COLORS = "use_system_colors"
|
||||
private val SELECTED_COLOR = "selected_color"
|
||||
private val USE_V4_API = "use_v4_api"
|
||||
private val SHOW_BACKDROP_GALLERY = "show_backdrop_gallery"
|
||||
}
|
||||
|
||||
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
|
||||
@@ -78,6 +80,8 @@ class AppPreferences(context: Context) {
|
||||
get() = preferences.getBoolean(USE_V4_API, true)
|
||||
set(value) { preferences.put(USE_V4_API, value) }
|
||||
|
||||
var showBackdropGallery: Boolean = true
|
||||
|
||||
private fun SharedPreferences.put(key: String, value: Any?) {
|
||||
edit().apply {
|
||||
when (value) {
|
||||
|
||||
@@ -183,63 +183,4 @@ fun PosterItem(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CoroutineCreationDuringComposition")
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun BackdropImage(
|
||||
modifier: Modifier = Modifier,
|
||||
imageUrl: String? = null,
|
||||
collection: ImageCollection? = null,
|
||||
contentDescription: String? = null
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
val gradient = Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background),
|
||||
startY = sizeImage.height.toFloat() / 3,
|
||||
endY = sizeImage.height.toFloat()
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
if (collection != null) {
|
||||
val pagerState = rememberPagerState()
|
||||
HorizontalPager(count = collection.backdrops.size, state = pagerState) { page ->
|
||||
val backdrop = collection.backdrops[page]
|
||||
AsyncImage(
|
||||
model = TmdbUtils.getFullBackdropPath(backdrop),
|
||||
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage = it.size }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (imageUrl != null) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage = it.size },
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage = it.size },
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(gradient)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,42 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.ui.components.BackdropImage
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.RatingRing
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.java.KoinJavaComponent.get
|
||||
|
||||
@Composable
|
||||
fun DetailHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
imageCollection: ImageCollection? = null,
|
||||
backdropUrl: String? = null,
|
||||
posterUrl: String? = null,
|
||||
backdropContentDescription: String? = null,
|
||||
@@ -47,16 +51,22 @@ fun DetailHeader(
|
||||
backdropImage, posterImage, ratingsView
|
||||
) = createRefs()
|
||||
|
||||
Backdrop(
|
||||
modifier = Modifier
|
||||
.constrainAs(backdropImage) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
imageUrl = backdropUrl,
|
||||
contentDescription = backdropContentDescription
|
||||
)
|
||||
if (imageCollection != null) {
|
||||
BackdropGallery(
|
||||
modifier = Modifier
|
||||
.constrainAs(backdropImage) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
imageCollection = imageCollection
|
||||
)
|
||||
} else {
|
||||
Backdrop(
|
||||
imageUrl = backdropUrl,
|
||||
contentDescription = backdropContentDescription
|
||||
)
|
||||
}
|
||||
|
||||
PosterItem(
|
||||
modifier = Modifier
|
||||
@@ -83,21 +93,95 @@ fun DetailHeader(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Backdrop(modifier: Modifier, imageUrl: String?, contentDescription: String? = null) {
|
||||
// val images = remember { mutableStateOf<ImageCollection?>(null) }
|
||||
// itemId?.let {
|
||||
// if (images.value == null) {
|
||||
// fetchImages(itemId, service, images)
|
||||
// }
|
||||
// }
|
||||
BackdropImage(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(230.dp),
|
||||
imageUrl = TmdbUtils.getFullBackdropPath(imageUrl),
|
||||
contentDescription = contentDescription
|
||||
// collection = images.value
|
||||
private fun BackdropContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (MutableState<IntSize>) -> Unit
|
||||
) {
|
||||
val sizeImage = remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
val gradient = Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background),
|
||||
startY = sizeImage.value.height.toFloat() / 3,
|
||||
endY = sizeImage.value.height.toFloat()
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
content(sizeImage)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(gradient)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Backdrop(
|
||||
modifier: Modifier = Modifier,
|
||||
imageUrl: String?,
|
||||
contentDescription: String? = null
|
||||
) {
|
||||
BackdropContainer(
|
||||
modifier = modifier
|
||||
) { sizeImage ->
|
||||
if (imageUrl != null) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage.value = it.size },
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage.value = it.size },
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
private fun BackdropGallery(
|
||||
modifier: Modifier,
|
||||
imageCollection: ImageCollection?
|
||||
) {
|
||||
BackdropContainer(
|
||||
modifier = modifier
|
||||
) { sizeImage ->
|
||||
if (imageCollection != null) {
|
||||
val pagerState = rememberPagerState()
|
||||
HorizontalPager(count = imageCollection.backdrops.size, state = pagerState) { page ->
|
||||
val backdrop = imageCollection.backdrops[page]
|
||||
AsyncImage(
|
||||
model = TmdbUtils.getFullBackdropPath(backdrop),
|
||||
placeholder = rememberAsyncImagePainter(model = R.drawable.placeholder),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.onGloballyPositioned { sizeImage.value = it.size }
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = pagerState.currentPage) {
|
||||
launch {
|
||||
delay(5000)
|
||||
with (pagerState) {
|
||||
val target = if (currentPage < pageCount - 1) currentPage + 1 else 0
|
||||
|
||||
animateScrollToPage(
|
||||
page = target,
|
||||
pageOffset = 0f
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -37,6 +37,7 @@ 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.model.*
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.*
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
|
||||
@@ -47,6 +48,7 @@ import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONObject
|
||||
import org.koin.java.KoinJavaComponent
|
||||
import java.text.DecimalFormat
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -54,7 +56,8 @@ import java.text.DecimalFormat
|
||||
fun MediaDetailView(
|
||||
appNavController: NavController,
|
||||
itemId: Int?,
|
||||
type: MediaViewType
|
||||
type: MediaViewType,
|
||||
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
|
||||
) {
|
||||
val service = when (type) {
|
||||
MediaViewType.MOVIE -> MoviesService()
|
||||
@@ -108,11 +111,19 @@ fun MediaDetailView(
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
val images = remember { mutableStateOf<ImageCollection?>(null) }
|
||||
itemId?.let {
|
||||
if (preferences.showBackdropGallery && images.value == null) {
|
||||
fetchImages(itemId, service, images)
|
||||
}
|
||||
}
|
||||
|
||||
DetailHeader(
|
||||
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
|
||||
posterContentDescription = mediaItem.value?.title,
|
||||
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
|
||||
rating = mediaItem.value?.voteAverage?.let { it / 10 }
|
||||
rating = mediaItem.value?.voteAverage?.let { it / 10 },
|
||||
imageCollection = images.value
|
||||
)
|
||||
|
||||
Column(
|
||||
@@ -995,7 +1006,7 @@ private fun fetchMediaItem(id: Int, service: DetailService, mediaItem: MutableSt
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchImages(id: Int, service: DetailService, images: MutableState<ImageCollection?>) {
|
||||
fun fetchImages(id: Int, service: DetailService, images: MutableState<ImageCollection?>) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = service.getImages(id)
|
||||
if (response.isSuccessful) {
|
||||
|
||||
Reference in New Issue
Block a user