mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-22 19:50:54 -05:00
display header information on details view
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
||||
|
||||
object TmdbUtils {
|
||||
|
||||
fun getFullPosterPath(posterPath: String?): String {
|
||||
return "https://image.tmdb.org/t/p/original${posterPath}"
|
||||
fun getFullPosterPath(posterPath: String?): String? {
|
||||
return posterPath?.let { "https://image.tmdb.org/t/p/original${posterPath}" }
|
||||
}
|
||||
|
||||
fun getFullPosterPath(tmdbItem: TmdbItem): String {
|
||||
return getFullPosterPath(tmdbItem.posterPath)
|
||||
fun getFullPosterPath(tmdbItem: TmdbItem?): String? {
|
||||
return tmdbItem?.let { getFullPosterPath(tmdbItem.posterPath) }
|
||||
}
|
||||
|
||||
fun getFullBackdropPath(backdropPath: String?): String? {
|
||||
return backdropPath?.let { "https://www.themoviedb.org/t/p/original${backdropPath}" }
|
||||
}
|
||||
|
||||
fun getFullBackdropPath(detailItem: DetailedItem?): String? {
|
||||
return detailItem?.let { getFullBackdropPath(detailItem.backdropPath) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
abstract class DetailedItem(id: Int, title: String, posterPath: String?): TmdbItem(id, title, posterPath)
|
||||
abstract class DetailedItem(
|
||||
id: Int,
|
||||
title: String,
|
||||
posterPath: String?,
|
||||
@Transient open val backdropPath: String?,
|
||||
@Transient open val genres: List<Genre>,
|
||||
@Transient open val overview: String?,
|
||||
@Transient open val productionCompanies: List<ProductionCompany>,
|
||||
@Transient open val status: String,
|
||||
@Transient open val tagline: String?
|
||||
): TmdbItem(id, title, posterPath)
|
||||
@@ -5,5 +5,16 @@ import com.google.gson.annotations.SerializedName
|
||||
class DetailedMovie(
|
||||
@SerializedName("id") override val id: Int,
|
||||
@SerializedName("original_title") override val title: String,
|
||||
@SerializedName("poster_path") override val posterPath: String?
|
||||
): DetailedItem(id, title, posterPath)
|
||||
@SerializedName("poster_path") override val posterPath: String?,
|
||||
@SerializedName("backdrop_path") override val backdropPath: String?,
|
||||
@SerializedName("genres") override val genres: List<Genre>,
|
||||
@SerializedName("overview") override val overview: String?,
|
||||
@SerializedName("production_companies") override val productionCompanies: List<ProductionCompany>,
|
||||
@SerializedName("status") override val status: String,
|
||||
@SerializedName("tagline") override val tagline: String?,
|
||||
@SerializedName("adult") val isAdult: Boolean,
|
||||
@SerializedName("budget") val budget: Int,
|
||||
@SerializedName("release_date") val releaseDate: String,
|
||||
@SerializedName("revenue") val revenue: Int,
|
||||
@SerializedName("runtime") val runtime: Int?
|
||||
): DetailedItem(id, title, posterPath, backdropPath, genres, overview, productionCompanies, status, tagline)
|
||||
|
||||
@@ -5,5 +5,18 @@ import com.google.gson.annotations.SerializedName
|
||||
class DetailedTv(
|
||||
@SerializedName("id") override val id: Int,
|
||||
@SerializedName("name") override val title: String,
|
||||
@SerializedName("poster_path") override val posterPath: String?
|
||||
): DetailedItem(id, title, posterPath)
|
||||
@SerializedName("poster_path") override val posterPath: String?,
|
||||
@SerializedName("backdrop_path") override val backdropPath: String?,
|
||||
@SerializedName("genres") override val genres: List<Genre>,
|
||||
@SerializedName("overview") override val overview: String?,
|
||||
@SerializedName("production_companies") override val productionCompanies: List<ProductionCompany>,
|
||||
@SerializedName("status") override val status: String,
|
||||
@SerializedName("tagline") override val tagline: String?,
|
||||
@SerializedName("created_by") val createdBy: List<Person>,
|
||||
@SerializedName("first_air_date") val firstAirDate: String,
|
||||
@SerializedName("in_production") val inProduction: Boolean,
|
||||
@SerializedName("networks") val networks: List<Network>,
|
||||
@SerializedName("number_of_episodes") val numberOfEpisodes: Int,
|
||||
@SerializedName("number_of_seasons") val numberOfSeasons: Int,
|
||||
@SerializedName("seasons") val seasons: List<Season>
|
||||
): DetailedItem(id, title, posterPath, backdropPath, genres, overview, productionCompanies, status, tagline)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Genre(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("name") val name: String
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Network(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("logo_path") val logoPath: String?,
|
||||
@SerializedName("origin_country") val originCountry: String
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
open class Person(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("credit_id") val creditId: Int,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("gender") val gender: Int
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ProductionCompany(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("logo_path") val logoPath: String?
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Season(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("air_date") val airDate: String,
|
||||
@SerializedName("episode_count") val episodeCount: Int,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("overview") val overview: String,
|
||||
@SerializedName("poster_path") val posterPath: String,
|
||||
@SerializedName("season_number") val seasonNumber: Int
|
||||
)
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.owenlejeune.tvtime.extensions
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.lazy.LazyGridScope
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -34,4 +36,9 @@ fun <T: Any> LazyListScope.listItems(
|
||||
items(items.size) { index ->
|
||||
itemContent(items[index])
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Color.unlessDarkMode(other: Color): Color {
|
||||
return if (isSystemInDarkTheme()) this else other
|
||||
}
|
||||
@@ -2,23 +2,24 @@ package com.owenlejeune.tvtime.ui.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.rememberImagePainter
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.owenlejeune.tvtime.R
|
||||
@@ -26,8 +27,6 @@ import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.screens.DetailViewType
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -83,4 +82,44 @@ fun PosterItem(
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackdropImage(
|
||||
modifier: Modifier = Modifier,
|
||||
imageUrl: String?
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
val gradient = Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, Color.Black),
|
||||
startY = sizeImage.height.toFloat() / 3,
|
||||
endY = sizeImage.height.toFloat()
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,30 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
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.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.NavController
|
||||
import com.owenlejeune.tvtime.api.tmdb.DetailService
|
||||
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.ui.components.BackdropImage
|
||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -25,6 +37,8 @@ fun DetailView(
|
||||
itemId: Int?,
|
||||
type: DetailViewType
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
|
||||
val service = when(type) {
|
||||
DetailViewType.MOVIE -> MoviesService()
|
||||
@@ -35,21 +49,69 @@ fun DetailView(
|
||||
}
|
||||
|
||||
ConstraintLayout(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
val (
|
||||
backButton,
|
||||
backdropImage,
|
||||
posterImage,
|
||||
title
|
||||
) = createRefs()
|
||||
|
||||
BackdropImage(
|
||||
modifier = Modifier
|
||||
.constrainAs(backdropImage) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.size(0.dp, 280.dp),
|
||||
imageUrl = TmdbUtils.getFullBackdropPath(mediaItem.value)
|
||||
)
|
||||
|
||||
PosterItem(
|
||||
mediaItem = mediaItem.value,
|
||||
modifier = Modifier
|
||||
.constrainAs(posterImage) {
|
||||
top.linkTo(parent.top, margin = 16.dp)
|
||||
bottom.linkTo(title.top, margin = 8.dp)
|
||||
start.linkTo(parent.start, margin = 16.dp)
|
||||
top.linkTo(backButton.bottom)
|
||||
}
|
||||
)
|
||||
|
||||
Text(
|
||||
text = mediaItem.value?.title ?: "",
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.constrainAs(title) {
|
||||
bottom.linkTo(backdropImage.bottom, margin = 8.dp)
|
||||
start.linkTo(parent.start, margin = 16.dp)
|
||||
end.linkTo(parent.end, margin = 16.dp)
|
||||
}
|
||||
.padding(start = 16.dp, end = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Start,
|
||||
softWrap = true
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = { appNavController.popBackStack() },
|
||||
modifier = Modifier
|
||||
.constrainAs(backButton) {
|
||||
top.linkTo(parent.top, 8.dp)
|
||||
start.linkTo(parent.start, 12.dp)
|
||||
}
|
||||
.wrapContentSize()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user