mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-19 10:11:13 -05:00
display cast and overview on details screen
This commit is contained in:
@@ -15,8 +15,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:theme="@style/Theme.TVTime"
|
||||||
android:theme="@style/Theme.TVTime">
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
package com.owenlejeune.tvtime
|
package com.owenlejeune.tvtime
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.animation.rememberSplineBasedDecay
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.owenlejeune.tvtime.ui.navigation.BottomNavigationRoutes
|
|
||||||
import com.owenlejeune.tvtime.ui.navigation.MainNavigationRoutes
|
import com.owenlejeune.tvtime.ui.navigation.MainNavigationRoutes
|
||||||
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
|
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
|
||||||
|
|
||||||
@@ -31,7 +23,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
val displayUnderStatusBar = remember { mutableStateOf(false) }
|
val displayUnderStatusBar = remember { mutableStateOf(false) }
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
|
// WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
|
||||||
|
// val statusBarColor = if (displayUnderStatusBar.value) {
|
||||||
|
// Color.Transparent
|
||||||
|
// } else {
|
||||||
|
// MaterialTheme.colorScheme.background
|
||||||
|
// }
|
||||||
|
// val systemUiController = rememberSystemUiController()
|
||||||
|
// systemUiController.setStatusBarColor(statusBarColor, !isSystemInDarkTheme())
|
||||||
MyApp(displayUnderStatusBar = displayUnderStatusBar)
|
MyApp(displayUnderStatusBar = displayUnderStatusBar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
@@ -10,4 +11,6 @@ interface DetailService {
|
|||||||
|
|
||||||
suspend fun getImages(id: Int): Response<ImageCollection>
|
suspend fun getImages(id: Int): Response<ImageCollection>
|
||||||
|
|
||||||
|
suspend fun getCastAndCrew(id: Int): Response<CastAndCrew>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedMovie
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailedMovie
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularMoviesResponse
|
import com.owenlejeune.tvtime.api.tmdb.model.PopularMoviesResponse
|
||||||
@@ -19,4 +20,7 @@ interface MoviesApi {
|
|||||||
@GET("movie/{id}/images")
|
@GET("movie/{id}/images")
|
||||||
suspend fun getMovieImages(@Path("id") id: Int): Response<ImageCollection>
|
suspend fun getMovieImages(@Path("id") id: Int): Response<ImageCollection>
|
||||||
|
|
||||||
|
@GET("movie/{id}/credits")
|
||||||
|
suspend fun getCastAndCrew(@Path("id") id: Int): Response<CastAndCrew>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@@ -19,4 +20,8 @@ class MoviesService: KoinComponent, DetailService {
|
|||||||
return service.getMovieImages(id)
|
return service.getMovieImages(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getCastAndCrew(id: Int): Response<CastAndCrew> {
|
||||||
|
return service.getCastAndCrew(id)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,15 @@ package com.owenlejeune.tvtime.api.tmdb
|
|||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.Image
|
import com.owenlejeune.tvtime.api.tmdb.model.Image
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.Person
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
||||||
|
|
||||||
object TmdbUtils {
|
object TmdbUtils {
|
||||||
|
|
||||||
|
private const val POSTER_BASE = "https://image.tmdb.org/t/p/original"
|
||||||
|
private const val BACKDROP_BASE = "https://www.themoviedb.org/t/p/original"
|
||||||
|
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
||||||
|
|
||||||
fun getFullPosterPath(posterPath: String?): String? {
|
fun getFullPosterPath(posterPath: String?): String? {
|
||||||
return posterPath?.let { "https://image.tmdb.org/t/p/original${posterPath}" }
|
return posterPath?.let { "https://image.tmdb.org/t/p/original${posterPath}" }
|
||||||
}
|
}
|
||||||
@@ -30,4 +35,12 @@ object TmdbUtils {
|
|||||||
return getFullBackdropPath(image.filePath)
|
return getFullBackdropPath(image.filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFullPersonImagePath(path: String?): String? {
|
||||||
|
return path?.let { "https://www.themoviedb.org/t/p/w600_and_h900_bestv2${path}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFullPersonImagePath(person: Person): String? {
|
||||||
|
return getFullPersonImagePath(person.profilePath)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularTvResponse
|
import com.owenlejeune.tvtime.api.tmdb.model.PopularTvResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedTv
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailedTv
|
||||||
@@ -19,4 +20,7 @@ interface TvApi {
|
|||||||
@GET("tv/{id}/images")
|
@GET("tv/{id}/images")
|
||||||
suspend fun getTvImages(@Path("id") id: Int): Response<ImageCollection>
|
suspend fun getTvImages(@Path("id") id: Int): Response<ImageCollection>
|
||||||
|
|
||||||
|
@GET("tv/{id}/credits")
|
||||||
|
suspend fun getCastAndCrew(@Path("id") id: Int): Response<CastAndCrew>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@@ -18,4 +19,8 @@ class TvService: KoinComponent, DetailService {
|
|||||||
override suspend fun getImages(id: Int): Response<ImageCollection> {
|
override suspend fun getImages(id: Int): Response<ImageCollection> {
|
||||||
return service.getTvImages(id)
|
return service.getTvImages(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getCastAndCrew(id: Int): Response<CastAndCrew> {
|
||||||
|
return service.getCastAndCrew(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class CastAndCrew(
|
||||||
|
@SerializedName("cast") val cast: List<CastMember>,
|
||||||
|
@SerializedName("crew") val crew: List<CrewMember>
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class CastMember(
|
||||||
|
@SerializedName("character") val character: String,
|
||||||
|
@SerializedName("order") val order: Int,
|
||||||
|
id: Int,
|
||||||
|
creditId: String,
|
||||||
|
name: String,
|
||||||
|
gender: Int,
|
||||||
|
profilePath: String?
|
||||||
|
): Person(id, creditId, name, gender, profilePath)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class CrewMember(
|
||||||
|
@SerializedName("department") val department: String,
|
||||||
|
@SerializedName("job") val job: String,
|
||||||
|
id: Int,
|
||||||
|
creditId: String,
|
||||||
|
name: String,
|
||||||
|
gender: Int,
|
||||||
|
profilePath: String?
|
||||||
|
): Person(id, creditId, name, gender, profilePath)
|
||||||
@@ -4,7 +4,8 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
open class Person(
|
open class Person(
|
||||||
@SerializedName("id") val id: Int,
|
@SerializedName("id") val id: Int,
|
||||||
@SerializedName("credit_id") val creditId: Int,
|
@SerializedName("credit_id") val creditId: String,
|
||||||
@SerializedName("name") val name: String,
|
@SerializedName("name") val name: String,
|
||||||
@SerializedName("gender") val gender: Int
|
@SerializedName("gender") val gender: Int,
|
||||||
|
@SerializedName("profile_path") val profilePath: String?
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.GridCells
|
import androidx.compose.foundation.lazy.GridCells
|
||||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -67,6 +69,13 @@ fun PosterItem(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val poster = mediaItem?.let { TmdbUtils.getFullPosterPath(mediaItem) }
|
val poster = mediaItem?.let { TmdbUtils.getFullPosterPath(mediaItem) }
|
||||||
|
Card(
|
||||||
|
elevation = 8.dp,
|
||||||
|
modifier = modifier
|
||||||
|
.size(width = width, height = height)
|
||||||
|
.padding(5.dp),
|
||||||
|
shape = RoundedCornerShape(5.dp)
|
||||||
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = if (mediaItem != null) {
|
painter = if (mediaItem != null) {
|
||||||
rememberImagePainter(
|
rememberImagePainter(
|
||||||
@@ -80,15 +89,15 @@ fun PosterItem(
|
|||||||
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
|
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
|
||||||
},
|
},
|
||||||
contentDescription = mediaItem?.title,
|
contentDescription = mediaItem?.title,
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.size(width = width, height = height)
|
.size(width = width, height = height)
|
||||||
.padding(5.dp)
|
|
||||||
.clickable {
|
.clickable {
|
||||||
mediaItem?.let {
|
mediaItem?.let {
|
||||||
onClick(mediaItem.id)
|
onClick(mediaItem.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("CoroutineCreationDuringComposition")
|
@SuppressLint("CoroutineCreationDuringComposition")
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar
|
|||||||
NavHost(navController = navController, startDestination = MainNavItem.MainView.route) {
|
NavHost(navController = navController, startDestination = MainNavItem.MainView.route) {
|
||||||
composable(MainNavItem.MainView.route) {
|
composable(MainNavItem.MainView.route) {
|
||||||
displayUnderStatusBar.value = false
|
displayUnderStatusBar.value = false
|
||||||
val systemUiController = rememberSystemUiController()
|
|
||||||
systemUiController.setStatusBarColor(MaterialTheme.colorScheme.background, !isSystemInDarkTheme())
|
|
||||||
MainAppView(appNavController = navController)
|
MainAppView(appNavController = navController)
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
@@ -38,8 +36,6 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar
|
|||||||
)
|
)
|
||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
displayUnderStatusBar.value = true
|
displayUnderStatusBar.value = true
|
||||||
val systemUiController = rememberSystemUiController()
|
|
||||||
systemUiController.setStatusBarColor(Color.Transparent, !isSystemInDarkTheme())
|
|
||||||
val args = navBackStackEntry.arguments
|
val args = navBackStackEntry.arguments
|
||||||
DetailView(
|
DetailView(
|
||||||
appNavController = navController,
|
appNavController = navController,
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens
|
package com.owenlejeune.tvtime.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -16,17 +22,23 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import coil.compose.rememberImagePainter
|
||||||
|
import coil.transform.RoundedCornersTransformation
|
||||||
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.DetailService
|
import com.owenlejeune.tvtime.api.tmdb.DetailService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.MoviesService
|
import com.owenlejeune.tvtime.api.tmdb.MoviesService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
|
import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TvService
|
import com.owenlejeune.tvtime.api.tmdb.TvService
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
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.api.tmdb.model.ImageCollection
|
||||||
|
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||||
import com.owenlejeune.tvtime.ui.components.BackdropImage
|
import com.owenlejeune.tvtime.ui.components.BackdropImage
|
||||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -48,24 +60,32 @@ fun DetailView(
|
|||||||
|
|
||||||
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
|
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
|
||||||
itemId?.let {
|
itemId?.let {
|
||||||
|
if (mediaItem.value == null) {
|
||||||
fetchMediaItem(itemId, service, mediaItem)
|
fetchMediaItem(itemId, service, mediaItem)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val images = remember { mutableStateOf<ImageCollection?>(null) }
|
val images = remember { mutableStateOf<ImageCollection?>(null) }
|
||||||
itemId?.let {
|
itemId?.let {
|
||||||
|
if (images.value == null) {
|
||||||
fetchImages(itemId, service, images)
|
fetchImages(itemId, service, images)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
ConstraintLayout(
|
ConstraintLayout(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(color = MaterialTheme.colorScheme.background)
|
.background(color = MaterialTheme.colorScheme.background)
|
||||||
|
.verticalScroll(state = scrollState)
|
||||||
) {
|
) {
|
||||||
val (
|
val (
|
||||||
backButton,
|
backButton,
|
||||||
backdropImage,
|
backdropImage,
|
||||||
posterImage,
|
posterImage,
|
||||||
title
|
titleText,
|
||||||
|
contentColumn
|
||||||
) = createRefs()
|
) = createRefs()
|
||||||
|
|
||||||
BackdropImage(
|
BackdropImage(
|
||||||
@@ -75,18 +95,18 @@ fun DetailView(
|
|||||||
start.linkTo(parent.start)
|
start.linkTo(parent.start)
|
||||||
}
|
}
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.size(0.dp, 280.dp),
|
.height(280.dp),
|
||||||
imageUrl = TmdbUtils.getFullBackdropPath(mediaItem.value),
|
imageUrl = TmdbUtils.getFullBackdropPath(mediaItem.value),
|
||||||
collection = images.value
|
// collection = images.value
|
||||||
)
|
)
|
||||||
|
|
||||||
PosterItem(
|
PosterItem(
|
||||||
mediaItem = mediaItem.value,
|
mediaItem = mediaItem.value,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.constrainAs(posterImage) {
|
.constrainAs(posterImage) {
|
||||||
bottom.linkTo(title.top, margin = 8.dp)
|
bottom.linkTo(backdropImage.bottom)
|
||||||
start.linkTo(parent.start, margin = 16.dp)
|
start.linkTo(parent.start, margin = 16.dp)
|
||||||
top.linkTo(backButton.bottom, margin = 8.dp)
|
top.linkTo(backButton.bottom)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,14 +114,14 @@ fun DetailView(
|
|||||||
text = mediaItem.value?.title ?: "",
|
text = mediaItem.value?.title ?: "",
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.constrainAs(title) {
|
.constrainAs(titleText) {
|
||||||
bottom.linkTo(backdropImage.bottom, margin = 8.dp)
|
bottom.linkTo(posterImage.bottom)
|
||||||
start.linkTo(parent.start, margin = 20.dp)
|
start.linkTo(posterImage.end, margin = 8.dp)
|
||||||
end.linkTo(parent.end, margin = 16.dp)
|
end.linkTo(parent.end, margin = 16.dp)
|
||||||
}
|
}
|
||||||
.padding(start = 16.dp, end = 16.dp)
|
.padding(start = 16.dp, end = 16.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(.6f),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Start,
|
||||||
softWrap = true
|
softWrap = true
|
||||||
)
|
)
|
||||||
@@ -110,10 +130,18 @@ fun DetailView(
|
|||||||
onClick = { appNavController.popBackStack() },
|
onClick = { appNavController.popBackStack() },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.constrainAs(backButton) {
|
.constrainAs(backButton) {
|
||||||
top.linkTo(parent.top, 16.dp)
|
top.linkTo(parent.top)//, 8.dp)
|
||||||
start.linkTo(parent.start, 12.dp)
|
start.linkTo(parent.start, 12.dp)
|
||||||
|
bottom.linkTo(posterImage.top)
|
||||||
}
|
}
|
||||||
.background(brush = Brush.radialGradient(colors = listOf(Color.Black, Color.Transparent)))
|
.background(
|
||||||
|
brush = Brush.radialGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Black,
|
||||||
|
Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.wrapContentSize()
|
.wrapContentSize()
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -122,6 +150,108 @@ fun DetailView(
|
|||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val castAndCrew = remember { mutableStateOf<CastAndCrew?>(null) }
|
||||||
|
itemId?.let {
|
||||||
|
if (castAndCrew.value == null) {
|
||||||
|
fetchCastAndCrew(itemId, service, castAndCrew)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.constrainAs(contentColumn) {
|
||||||
|
top.linkTo(backdropImage.bottom, margin = 8.dp)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
elevation = 8.dp
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(vertical = 12.dp, horizontal = 16.dp),
|
||||||
|
text = mediaItem.value?.overview ?: "",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
elevation = 8.dp
|
||||||
|
) {
|
||||||
|
LazyRow(modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
items(castAndCrew.value?.cast?.size ?: 0) { i ->
|
||||||
|
val castMember = castAndCrew.value!!.cast[i]
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(124.dp)
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(end = 12.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(width = 120.dp, height = 180.dp),
|
||||||
|
painter = rememberImagePainter(
|
||||||
|
data = TmdbUtils.getFullPersonImagePath(castMember),
|
||||||
|
builder = {
|
||||||
|
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
||||||
|
placeholder(R.drawable.placeholder)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
val nameLineHeight = MaterialTheme.typography.bodyMedium.fontSize*4/3
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
.sizeIn(
|
||||||
|
minHeight = with(LocalDensity.current) {
|
||||||
|
(nameLineHeight * 2).toDp()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
text = castMember.name,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
lineHeight = nameLineHeight
|
||||||
|
)
|
||||||
|
val characterLineHeight = MaterialTheme.typography.bodySmall.fontSize*4/3
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.sizeIn(
|
||||||
|
minHeight = with(LocalDensity.current) {
|
||||||
|
(characterLineHeight * 2).toDp()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
text = castMember.character,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
lineHeight = characterLineHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +277,17 @@ private fun fetchImages(id: Int, service: DetailService, images: MutableState<Im
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun fetchCastAndCrew(id: Int, service: DetailService, castAndCrew: MutableState<CastAndCrew?>) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val response = service.getCastAndCrew(id)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
castAndCrew.value = response.body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class DetailViewType {
|
enum class DetailViewType {
|
||||||
MOVIE,
|
MOVIE,
|
||||||
TV
|
TV
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
import com.owenlejeune.tvtime.ui.components.PaletteView
|
import com.owenlejeune.tvtime.ui.components.PaletteView
|
||||||
@@ -67,7 +68,7 @@ private fun PaletteDialog(showDialog: MutableState<Boolean>) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = { showDialog.value = false }
|
onClick = { showDialog.value = false }
|
||||||
) {
|
) {
|
||||||
Text(text = "Dismiss")
|
Text(text = "Dismiss", color = Color.White)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {},
|
confirmButton = {},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<corners android:radius="5dp" />
|
<corners android:radius="20dp" />
|
||||||
<solid android:color="@android:color/darker_gray" />
|
<solid android:color="@android:color/darker_gray" />
|
||||||
</shape>
|
</shape>
|
||||||
Reference in New Issue
Block a user