display all backdrops as scrolling view

This commit is contained in:
Owen LeJeune
2022-02-15 11:07:13 -05:00
parent a194fa57b0
commit 86034bf198
12 changed files with 127 additions and 30 deletions

View File

@@ -63,6 +63,7 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling-preview:${Versions.compose}"
implementation "androidx.activity:activity-compose:${Versions.activity_compose}"
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
implementation "com.google.accompanist:accompanist-pager:${Versions.compose_accompanist}"
implementation "androidx.navigation:navigation-compose:${Versions.compose_navigation}"
implementation "androidx.paging:paging-compose:${Versions.compose_paging}"
implementation "androidx.constraintlayout:constraintlayout-compose:${Versions.compose_constraint_layout}"

View File

@@ -31,9 +31,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
val displayUnderStatusBar = remember { mutableStateOf(false) }
// if (displayUnderStatusBar.value) {
WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
// }
WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
MyApp(displayUnderStatusBar = displayUnderStatusBar)
}
}

View File

@@ -1,10 +1,13 @@
package com.owenlejeune.tvtime.api.tmdb
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
import retrofit2.Response
interface DetailService {
suspend fun getById(id: Int): Response<DetailedItem>
suspend fun getById(id: Int): Response<out DetailedItem>
suspend fun getImages(id: Int): Response<ImageCollection>
}

View File

@@ -1,7 +1,7 @@
package com.owenlejeune.tvtime.api.tmdb
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.DetailedMovie
import com.owenlejeune.tvtime.api.tmdb.model.PopularMovie
import com.owenlejeune.tvtime.api.tmdb.model.PopularMoviesResponse
import retrofit2.Response
import retrofit2.http.GET
@@ -16,4 +16,7 @@ interface MoviesApi {
@GET("movie/{id}")
suspend fun getMovieById(@Path("id") id: Int): Response<DetailedMovie>
@GET("movie/{id}/images")
suspend fun getMovieImages(@Path("id") id: Int): Response<ImageCollection>
}

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.api.tmdb
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
import org.koin.core.component.KoinComponent
import retrofit2.Response
@@ -10,6 +11,12 @@ class MoviesService: KoinComponent, DetailService {
suspend fun getPopularMovies(page: Int = 1) = service.getPopularMovies(page)
override suspend fun getById(id: Int): Response<DetailedItem> = service.getMovieById(id) as Response<DetailedItem>
override suspend fun getById(id: Int): Response<out DetailedItem> {
return service.getMovieById(id)
}
override suspend fun getImages(id: Int): Response<ImageCollection> {
return service.getMovieImages(id)
}
}

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime.api.tmdb
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
import com.owenlejeune.tvtime.api.tmdb.model.Image
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
object TmdbUtils {
@@ -13,6 +14,10 @@ object TmdbUtils {
return tmdbItem?.let { getFullPosterPath(tmdbItem.posterPath) }
}
fun getFullPosterPath(image: Image): String? {
return getFullPosterPath(image.filePath)
}
fun getFullBackdropPath(backdropPath: String?): String? {
return backdropPath?.let { "https://www.themoviedb.org/t/p/original${backdropPath}" }
}
@@ -21,4 +26,8 @@ object TmdbUtils {
return detailItem?.let { getFullBackdropPath(detailItem.backdropPath) }
}
fun getFullBackdropPath(image: Image): String? {
return getFullBackdropPath(image.filePath)
}
}

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.api.tmdb
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.PopularTvResponse
import com.owenlejeune.tvtime.api.tmdb.model.DetailedTv
import retrofit2.Response
@@ -13,6 +14,9 @@ interface TvApi {
suspend fun getPoplarTv(@Query("page") page: Int = 1): Response<PopularTvResponse>
@GET("tv/{id}")
suspend fun getTvShowById(@Path("id") id: Int): Response<DetailedTv>
suspend fun getTvShowById(@Path("id") id: Int): Response<out DetailedTv>
@GET("tv/{id}/images")
suspend fun getTvImages(@Path("id") id: Int): Response<ImageCollection>
}

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.api.tmdb
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
import org.koin.core.component.KoinComponent
import retrofit2.Response
@@ -10,7 +11,11 @@ class TvService: KoinComponent, DetailService {
suspend fun getPopularTv(page: Int = 1) = service.getPoplarTv(page)
override suspend fun getById(id: Int): Response<DetailedItem> {
return service.getTvShowById(id) as Response<DetailedItem>
override suspend fun getById(id: Int): Response<out DetailedItem> {
return service.getTvShowById(id)
}
override suspend fun getImages(id: Int): Response<ImageCollection> {
return service.getTvImages(id)
}
}

View File

@@ -0,0 +1,9 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
data class Image(
@SerializedName("file_path") val filePath: String,
@SerializedName("height") val height: Int,
@SerializedName("width") val width: Int
)

View File

@@ -0,0 +1,8 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
data class ImageCollection(
@SerializedName("backdrops") val backdrops: List<Image>,
@SerializedName("posters") val posters: List<Image>
)

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.ui.components
import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
@@ -22,11 +24,16 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import coil.compose.rememberImagePainter
import coil.transform.RoundedCornersTransformation
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.api.tmdb.TmdbUtils
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
import com.owenlejeune.tvtime.extensions.dpToPx
import com.owenlejeune.tvtime.extensions.listItems
import kotlinx.coroutines.*
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -84,17 +91,20 @@ fun PosterItem(
)
}
@SuppressLint("CoroutineCreationDuringComposition")
@OptIn(ExperimentalPagerApi::class)
@Composable
fun BackdropImage(
modifier: Modifier = Modifier,
imageUrl: String?
imageUrl: String? = null,
collection: ImageCollection? = null
) {
val context = LocalContext.current
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
val gradient = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black),
colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background),
startY = sizeImage.height.toFloat() / 3,
endY = sizeImage.height.toFloat()
)
@@ -102,24 +112,45 @@ fun BackdropImage(
Box(
modifier = modifier
) {
Image(
painter = if (imageUrl != null) {
rememberImagePainter(
data = imageUrl,
builder = {
placeholder(R.drawable.placeholder)
if (collection != null) {
val pagerState = rememberPagerState()
HorizontalPager(count = collection.backdrops.size, state = pagerState) { page ->
val backdrop = collection.backdrops[page]
Image(
painter = rememberImagePainter(
data = TmdbUtils.getFullBackdropPath(backdrop),
builder = {
placeholder(R.drawable.placeholder)
}
),
contentDescription = "",
modifier = Modifier.onGloballyPositioned {
sizeImage = it.size
}
)
} else {
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
},
contentDescription = "",
modifier = Modifier.onGloballyPositioned {
sizeImage = it.size
}
)
} else {
Image(
painter = if (imageUrl != null) {
rememberImagePainter(
data = imageUrl,
builder = {
placeholder(R.drawable.placeholder)
}
)
} else {
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
},
contentDescription = "",
modifier = Modifier.onGloballyPositioned {
sizeImage = it.size
}
)
}
Box(
modifier = Modifier.matchParentSize().background(gradient)
modifier = Modifier
.matchParentSize()
.background(gradient)
)
}
}

View File

@@ -25,6 +25,8 @@ import com.owenlejeune.tvtime.api.tmdb.MoviesService
import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
import com.owenlejeune.tvtime.api.tmdb.TvService
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
import com.owenlejeune.tvtime.api.tmdb.model.Image
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.ui.components.BackdropImage
import com.owenlejeune.tvtime.ui.components.PosterItem
import kotlinx.coroutines.CoroutineScope
@@ -39,16 +41,21 @@ fun DetailView(
type: DetailViewType
) {
val context = LocalContext.current
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
val service = when(type) {
DetailViewType.MOVIE -> MoviesService()
DetailViewType.TV -> TvService()
}
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
itemId?.let {
fetchMediaItem(itemId, service, mediaItem)
}
val images = remember { mutableStateOf<ImageCollection?>(null) }
itemId?.let {
fetchImages(itemId, service, images)
}
ConstraintLayout(
modifier = Modifier
.fillMaxSize()
@@ -69,7 +76,8 @@ fun DetailView(
}
.fillMaxWidth()
.size(0.dp, 280.dp),
imageUrl = TmdbUtils.getFullBackdropPath(mediaItem.value)
imageUrl = TmdbUtils.getFullBackdropPath(mediaItem.value),
collection = images.value
)
PosterItem(
@@ -78,7 +86,7 @@ fun DetailView(
.constrainAs(posterImage) {
bottom.linkTo(title.top, margin = 8.dp)
start.linkTo(parent.start, margin = 16.dp)
top.linkTo(backButton.bottom)
top.linkTo(backButton.bottom, margin = 8.dp)
}
)
@@ -122,7 +130,18 @@ private fun fetchMediaItem(id: Int, service: DetailService, mediaItem: MutableSt
val response = service.getById(id)
if (response.isSuccessful) {
withContext(Dispatchers.Main) {
mediaItem.value = response.body()!!
mediaItem.value = response.body()
}
}
}
}
private fun fetchImages(id: Int, service: DetailService, images: MutableState<ImageCollection?>) {
CoroutineScope(Dispatchers.IO).launch {
val response = service.getImages(id)
if (response.isSuccessful) {
withContext(Dispatchers.Main) {
images.value = response.body()
}
}
}