mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-15 00:02:48 -05:00
display all backdrops as scrolling view
This commit is contained in:
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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>
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user