refactor support for paging backdrop images

This commit is contained in:
Owen LeJeune
2022-08-31 23:25:53 -04:00
parent 8ef1d05965
commit ec12621743
6 changed files with 160 additions and 124 deletions

View File

@@ -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 {

View File

@@ -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 ->

View File

@@ -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) {

View File

@@ -184,62 +184,3 @@ 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)
)
}
}

View File

@@ -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(
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

View File

@@ -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) {