mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-11 06:12:44 -05:00
add beginning person page implementation
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailPerson
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.PersonCreditsResponse
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.PersonImageCollection
|
||||||
|
import retrofit2.Response
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
interface PeopleApi {
|
||||||
|
|
||||||
|
@GET("person/{id}")
|
||||||
|
suspend fun getPerson(@Path("id") id: Int): Response<DetailPerson>
|
||||||
|
|
||||||
|
// @GET("person/{id}/movie_credits")
|
||||||
|
// suspend fun getMovieCredits(@Path("id") id: Int)
|
||||||
|
//
|
||||||
|
// @GET("person/{id}/tv_credits")
|
||||||
|
// suspend fun getTvCredits(@Path("id") id: Int)
|
||||||
|
|
||||||
|
@GET("person/{id}/combined_credits")
|
||||||
|
suspend fun getCredits(@Path("id") id: Int): Response<PersonCreditsResponse>
|
||||||
|
|
||||||
|
@GET("person/{id}/images")
|
||||||
|
suspend fun getImages(@Path("id") id: Int): Response<PersonImageCollection>
|
||||||
|
|
||||||
|
// @GET("persons/{id}/tagged_images")
|
||||||
|
// suspend fun getTaggedImages(@Path("id") id: Int, @Query("page") page: Int = 1): Response<PersonImageCollection>
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.DetailPerson
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.PersonCreditsResponse
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.PersonImageCollection
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
|
class PeopleService: KoinComponent {
|
||||||
|
|
||||||
|
private val service by lazy { TmdbClient().createPeopleService() }
|
||||||
|
|
||||||
|
suspend fun getPerson(id: Int): Response<DetailPerson> {
|
||||||
|
return service.getPerson(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCredits(id: Int): Response<PersonCreditsResponse> {
|
||||||
|
return service.getCredits(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getImages(id: Int): Response<PersonImageCollection> {
|
||||||
|
return service.getImages(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -33,6 +33,10 @@ class TmdbClient: KoinComponent {
|
|||||||
return client.create(TvApi::class.java)
|
return client.create(TvApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPeopleService(): PeopleApi {
|
||||||
|
return client.create(PeopleApi::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
private inner class TmdbInterceptor: Interceptor {
|
private inner class TmdbInterceptor: Interceptor {
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val apiParam = QueryParam("api_key", BuildConfig.TMDB_ApiKey)
|
val apiParam = QueryParam("api_key", BuildConfig.TMDB_ApiKey)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import com.google.gson.annotations.SerializedName
|
|||||||
class CastMember(
|
class CastMember(
|
||||||
@SerializedName("character") val character: String,
|
@SerializedName("character") val character: String,
|
||||||
@SerializedName("order") val order: Int,
|
@SerializedName("order") val order: Int,
|
||||||
|
@SerializedName("credit_id") val creditId: String,
|
||||||
id: Int,
|
id: Int,
|
||||||
creditId: String,
|
|
||||||
name: String,
|
name: String,
|
||||||
gender: Int,
|
gender: Int,
|
||||||
profilePath: String?
|
profilePath: String?
|
||||||
): Person(id, creditId, name, gender, profilePath)
|
): Person(id, name, gender, profilePath)
|
||||||
@@ -5,9 +5,9 @@ import com.google.gson.annotations.SerializedName
|
|||||||
class CrewMember(
|
class CrewMember(
|
||||||
@SerializedName("department") val department: String,
|
@SerializedName("department") val department: String,
|
||||||
@SerializedName("job") val job: String,
|
@SerializedName("job") val job: String,
|
||||||
|
@SerializedName("credit_id") val creditId: String,
|
||||||
id: Int,
|
id: Int,
|
||||||
creditId: String,
|
|
||||||
name: String,
|
name: String,
|
||||||
gender: Int,
|
gender: Int,
|
||||||
profilePath: String?
|
profilePath: String?
|
||||||
): Person(id, creditId, name, gender, profilePath)
|
): Person(id, name, gender, profilePath)
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class DetailCast(
|
||||||
|
@SerializedName("id") val id: Int,
|
||||||
|
@SerializedName("episode_count") val episodeCount: Int,
|
||||||
|
@SerializedName("overview") val overview: String,
|
||||||
|
@SerializedName("name") val name: String,
|
||||||
|
@SerializedName("media_type") val mediaType: String,
|
||||||
|
@SerializedName("poster_path") val posterPath: String?,
|
||||||
|
@SerializedName("first_air_date") val firstAirDate: String,
|
||||||
|
@SerializedName("character") val character: String,
|
||||||
|
@SerializedName("title") val title: String,
|
||||||
|
@SerializedName("adult") val isAdult: Boolean,
|
||||||
|
@SerializedName("release_date") val releaseDate: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class DetailCrew(
|
||||||
|
@SerializedName("id") val id: Int,
|
||||||
|
@SerializedName("department") val department: String,
|
||||||
|
@SerializedName("episode_count") val episodeCount: Int,
|
||||||
|
@SerializedName("job") val job: String,
|
||||||
|
@SerializedName("overview") val overview: String,
|
||||||
|
@SerializedName("name") val name: String,
|
||||||
|
@SerializedName("media_type") val mediaType: String,
|
||||||
|
@SerializedName("first_air_date") val firstAirDate: String,
|
||||||
|
@SerializedName("poster_path") val posterPath: String,
|
||||||
|
@SerializedName("title") val title: String,
|
||||||
|
@SerializedName("adult") val isAdult: Boolean,
|
||||||
|
@SerializedName("release_date") val releaseDate: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class DetailPerson(
|
||||||
|
@SerializedName("birthday") val birthday: String,
|
||||||
|
@SerializedName("known_for_department") val knownFor: String,
|
||||||
|
@SerializedName("deathday") val dateOfDeath: String?,
|
||||||
|
@SerializedName("biography") val biography: String,
|
||||||
|
@SerializedName("place_of_birth") val birthplace: String?,
|
||||||
|
@SerializedName("adult") val isAdult: Boolean,
|
||||||
|
id: Int,
|
||||||
|
name: String,
|
||||||
|
gender: Int,
|
||||||
|
profilePath: String?
|
||||||
|
): Person(id, name, gender, profilePath)
|
||||||
@@ -4,7 +4,6 @@ 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: 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?
|
@SerializedName("profile_path") val profilePath: String?
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class PersonCreditsResponse(
|
||||||
|
@SerializedName("cast") val cast: List<DetailCast>,
|
||||||
|
@SerializedName("crew") val crew: List<DetailCrew>
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class PersonImage(
|
||||||
|
@SerializedName("aspect_ratio") val aspectRatio: Float,
|
||||||
|
@SerializedName("file_path") val filePath: String,
|
||||||
|
@SerializedName("width") val width: Int,
|
||||||
|
@SerializedName("height") val height: Int
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class PersonImageCollection(
|
||||||
|
@SerializedName("profiles") val images: List<PersonImage>
|
||||||
|
)
|
||||||
@@ -3,7 +3,6 @@ package com.owenlejeune.tvtime.ui.components
|
|||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -13,14 +12,10 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.rememberImagePainter
|
|
||||||
import coil.transform.RoundedCornersTransformation
|
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ContentCard(
|
fun ContentCard(
|
||||||
@@ -106,24 +101,19 @@ fun ImageTextCard(
|
|||||||
noDataImage: Int = R.drawable.placeholder,
|
noDataImage: Int = R.drawable.placeholder,
|
||||||
placeholder: Int = R.drawable.placeholder,
|
placeholder: Int = R.drawable.placeholder,
|
||||||
titleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
titleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
subtitleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant
|
subtitleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
onItemClicked: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
Column(modifier = modifier) {
|
||||||
Column(
|
PosterItem(
|
||||||
modifier = modifier
|
width = 120.dp,
|
||||||
.padding(end = 12.dp)
|
height = 180.dp,
|
||||||
) {
|
onClick = onItemClicked,
|
||||||
Image(
|
url = imageUrl,
|
||||||
modifier = Modifier
|
noDataImage = noDataImage,
|
||||||
.size(width = 120.dp, height = 180.dp),
|
placeholder = placeholder,
|
||||||
painter = rememberImagePainter(
|
contentDescription = title,
|
||||||
data = imageUrl ?: noDataImage,
|
elevation = 0.dp
|
||||||
builder = {
|
|
||||||
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
|
||||||
placeholder(placeholder)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentDescription = ""
|
|
||||||
)
|
)
|
||||||
MinLinesText(
|
MinLinesText(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.google.accompanist.pager.HorizontalPager
|
|||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.Person
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
||||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||||
import com.owenlejeune.tvtime.extensions.listItems
|
import com.owenlejeune.tvtime.extensions.listItems
|
||||||
@@ -54,6 +55,7 @@ fun PosterGrid(
|
|||||||
) {
|
) {
|
||||||
listItems(mediaList.value) { item ->
|
listItems(mediaList.value) { item ->
|
||||||
PosterItem(
|
PosterItem(
|
||||||
|
modifier = Modifier.padding(5.dp),
|
||||||
mediaItem = item,
|
mediaItem = item,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
)
|
)
|
||||||
@@ -66,38 +68,86 @@ fun PosterItem(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
width: Dp = POSTER_WIDTH,
|
width: Dp = POSTER_WIDTH,
|
||||||
height: Dp = POSTER_HEIGHT,
|
height: Dp = POSTER_HEIGHT,
|
||||||
onClick: (Int) -> Unit = {},
|
onClick: (id: Int) -> Unit = {},
|
||||||
|
elevation: Dp = 8.dp,
|
||||||
mediaItem: TmdbItem?
|
mediaItem: TmdbItem?
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
PosterItem(
|
||||||
val poster = mediaItem?.let { TmdbUtils.getFullPosterPath(mediaItem) }
|
modifier = modifier,
|
||||||
Card(
|
width = width,
|
||||||
|
height = height,
|
||||||
|
onClick = {
|
||||||
|
mediaItem?.let {
|
||||||
|
onClick(mediaItem.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url = mediaItem?.let { TmdbUtils.getFullPosterPath(mediaItem) },
|
||||||
|
elevation = elevation,
|
||||||
|
contentDescription = mediaItem?.title
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PosterItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
width: Dp = POSTER_WIDTH,
|
||||||
|
height: Dp = POSTER_HEIGHT,
|
||||||
|
onClick: (id: Int) -> Unit = {},
|
||||||
|
elevation: Dp = 8.dp,
|
||||||
|
person: Person?
|
||||||
|
) {
|
||||||
|
PosterItem(
|
||||||
|
modifier = modifier,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
onClick = {
|
||||||
|
person?.let {
|
||||||
|
onClick(person.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url = person?.let { TmdbUtils.getFullPersonImagePath(person) },
|
||||||
elevation = 8.dp,
|
elevation = 8.dp,
|
||||||
|
contentDescription = person?.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PosterItem(
|
||||||
|
url: String?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
width: Dp = POSTER_WIDTH,
|
||||||
|
height: Dp = POSTER_HEIGHT,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
noDataImage: Int = R.drawable.placeholder,
|
||||||
|
placeholder: Int = R.drawable.placeholder,
|
||||||
|
elevation: Dp = 8.dp,
|
||||||
|
contentDescription: String?
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Card(
|
||||||
|
elevation = elevation,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(width = width, height = height)
|
.size(width = width, height = height),
|
||||||
.padding(5.dp),
|
|
||||||
shape = RoundedCornerShape(5.dp)
|
shape = RoundedCornerShape(5.dp)
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = if (mediaItem != null) {
|
painter = if (url != null) {
|
||||||
rememberImagePainter(
|
rememberImagePainter(
|
||||||
data = poster,
|
data = url ?: noDataImage,
|
||||||
builder = {
|
builder = {
|
||||||
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
||||||
placeholder(R.drawable.placeholder)
|
placeholder(placeholder)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
|
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
|
||||||
},
|
},
|
||||||
contentDescription = mediaItem?.title,
|
contentDescription = contentDescription,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = width, height = height)
|
.size(width = width, height = height)
|
||||||
.clickable {
|
.clickable(
|
||||||
mediaItem?.let {
|
onClick = onClick
|
||||||
onClick(mediaItem.id)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.navigation.navArgument
|
|||||||
import com.owenlejeune.tvtime.ui.screens.DetailView
|
import com.owenlejeune.tvtime.ui.screens.DetailView
|
||||||
import com.owenlejeune.tvtime.ui.screens.MainAppView
|
import com.owenlejeune.tvtime.ui.screens.MainAppView
|
||||||
import com.owenlejeune.tvtime.ui.screens.MediaViewType
|
import com.owenlejeune.tvtime.ui.screens.MediaViewType
|
||||||
|
import com.owenlejeune.tvtime.ui.screens.PersonDetailView
|
||||||
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.FavouritesTab
|
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.FavouritesTab
|
||||||
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.MediaTab
|
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.MediaTab
|
||||||
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.SettingsTab
|
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.SettingsTab
|
||||||
@@ -36,11 +37,19 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar
|
|||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
displayUnderStatusBar.value = true
|
displayUnderStatusBar.value = true
|
||||||
val args = navBackStackEntry.arguments
|
val args = navBackStackEntry.arguments
|
||||||
DetailView(
|
val mediaType = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType
|
||||||
appNavController = navController,
|
if (mediaType != MediaViewType.PERSON) {
|
||||||
itemId = args?.getInt(NavConstants.ID_KEY),
|
DetailView(
|
||||||
type = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType
|
appNavController = navController,
|
||||||
)
|
itemId = args.getInt(NavConstants.ID_KEY),
|
||||||
|
type = mediaType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PersonDetailView(
|
||||||
|
appNavController = navController,
|
||||||
|
personId = args.getInt(NavConstants.ID_KEY)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ import androidx.navigation.NavController
|
|||||||
import com.owenlejeune.tvtime.R
|
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.PeopleService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TvService
|
import com.owenlejeune.tvtime.api.tmdb.TvService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.*
|
import com.owenlejeune.tvtime.api.tmdb.model.*
|
||||||
import com.owenlejeune.tvtime.extensions.listItems
|
import com.owenlejeune.tvtime.extensions.listItems
|
||||||
import com.owenlejeune.tvtime.ui.components.*
|
import com.owenlejeune.tvtime.ui.components.*
|
||||||
|
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -46,6 +48,7 @@ fun DetailView(
|
|||||||
val service = when(type) {
|
val service = when(type) {
|
||||||
MediaViewType.MOVIE -> MoviesService()
|
MediaViewType.MOVIE -> MoviesService()
|
||||||
MediaViewType.TV -> TvService()
|
MediaViewType.TV -> TvService()
|
||||||
|
else -> throw IllegalArgumentException("Media type given: ${type}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
|
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
|
||||||
@@ -92,7 +95,8 @@ fun DetailView(
|
|||||||
start.linkTo(posterImage.end, margin = 8.dp)
|
start.linkTo(posterImage.end, margin = 8.dp)
|
||||||
end.linkTo(parent.end, margin = 16.dp)
|
end.linkTo(parent.end, margin = 16.dp)
|
||||||
},
|
},
|
||||||
mediaItem = mediaItem
|
title = mediaItem.value?.title ?: ""
|
||||||
|
// mediaItem = mediaItem
|
||||||
)
|
)
|
||||||
|
|
||||||
BackButton(
|
BackButton(
|
||||||
@@ -111,11 +115,100 @@ fun DetailView(
|
|||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
mediaItem = mediaItem,
|
mediaItem = mediaItem,
|
||||||
service = service,
|
service = service,
|
||||||
mediaType = type
|
mediaType = type,
|
||||||
|
appNavController = appNavController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PersonDetailView(
|
||||||
|
appNavController: NavController,
|
||||||
|
personId: Int?
|
||||||
|
) {
|
||||||
|
val person = remember { mutableStateOf<DetailPerson?>(null) }
|
||||||
|
personId?.let {
|
||||||
|
if (person.value == null) {
|
||||||
|
fetchPerson(personId, person)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
ConstraintLayout(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = MaterialTheme.colorScheme.background)
|
||||||
|
.verticalScroll(state = scrollState)
|
||||||
|
) {
|
||||||
|
val (
|
||||||
|
backButton, backdropImage, profileImage, nameText, contentColumn
|
||||||
|
) = createRefs()
|
||||||
|
|
||||||
|
BackdropImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(280.dp)
|
||||||
|
.constrainAs(backdropImage) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
end.linkTo(parent.end)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
PosterItem(
|
||||||
|
person = person.value,
|
||||||
|
modifier = Modifier
|
||||||
|
.constrainAs(profileImage) {
|
||||||
|
bottom.linkTo(backdropImage.bottom)
|
||||||
|
start.linkTo(parent.start, margin = 16.dp)
|
||||||
|
top.linkTo(backButton.bottom)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
TitleText(
|
||||||
|
modifier = Modifier.constrainAs(nameText) {
|
||||||
|
bottom.linkTo(profileImage.bottom)
|
||||||
|
start.linkTo(profileImage.end, margin = 8.dp)
|
||||||
|
end.linkTo(parent.end, margin = 16.dp)
|
||||||
|
},
|
||||||
|
title = person.value?.name ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
BackButton(
|
||||||
|
modifier = Modifier.constrainAs(backButton) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
start.linkTo(parent.start, 12.dp)
|
||||||
|
bottom.linkTo(profileImage.top)
|
||||||
|
},
|
||||||
|
appNavController = appNavController
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||||
|
.constrainAs(contentColumn) {
|
||||||
|
top.linkTo(backdropImage.bottom)//, margin = 8.dp)
|
||||||
|
},
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
ContentCard {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(vertical = 12.dp, horizontal = 16.dp),
|
||||||
|
text = person.value?.biography ?: "",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Backdrop(modifier: Modifier, mediaItem: MutableState<DetailedItem?>) {
|
private fun Backdrop(modifier: Modifier, mediaItem: MutableState<DetailedItem?>) {
|
||||||
// val images = remember { mutableStateOf<ImageCollection?>(null) }
|
// val images = remember { mutableStateOf<ImageCollection?>(null) }
|
||||||
@@ -134,9 +227,9 @@ private fun Backdrop(modifier: Modifier, mediaItem: MutableState<DetailedItem?>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TitleText(modifier: Modifier, mediaItem: MutableState<DetailedItem?>) {
|
private fun TitleText(modifier: Modifier, title: String/*mediaItem: MutableState<DetailedItem?>*/) {
|
||||||
Text(
|
Text(
|
||||||
text = mediaItem.value?.title ?: "",
|
text = title,//mediaItem.value?.title ?: "",
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 16.dp, end = 16.dp)
|
.padding(start = 16.dp, end = 16.dp)
|
||||||
@@ -171,7 +264,8 @@ private fun ContentColumn(
|
|||||||
itemId: Int?,
|
itemId: Int?,
|
||||||
mediaItem: MutableState<DetailedItem?>,
|
mediaItem: MutableState<DetailedItem?>,
|
||||||
service: DetailService,
|
service: DetailService,
|
||||||
mediaType: MediaViewType
|
mediaType: MediaViewType,
|
||||||
|
appNavController: NavController
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -190,7 +284,7 @@ private fun ContentColumn(
|
|||||||
OverviewCard(mediaItem = mediaItem)
|
OverviewCard(mediaItem = mediaItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
CastCard(itemId = itemId, service = service)
|
CastCard(itemId = itemId, service = service, appNavController = appNavController)
|
||||||
|
|
||||||
SimilarContentCard(itemId = itemId, service = service)
|
SimilarContentCard(itemId = itemId, service = service)
|
||||||
|
|
||||||
@@ -296,7 +390,7 @@ private fun OverviewCard(mediaItem: MutableState<DetailedItem?>, modifier: Modif
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CastCard(itemId: Int?, service: DetailService, modifier: Modifier = Modifier) {
|
private fun CastCard(itemId: Int?, service: DetailService, appNavController: NavController, modifier: Modifier = Modifier) {
|
||||||
val castAndCrew = remember { mutableStateOf<CastAndCrew?>(null) }
|
val castAndCrew = remember { mutableStateOf<CastAndCrew?>(null) }
|
||||||
itemId?.let {
|
itemId?.let {
|
||||||
if (castAndCrew.value == null) {
|
if (castAndCrew.value == null) {
|
||||||
@@ -311,19 +405,20 @@ private fun CastCard(itemId: Int?, service: DetailService, modifier: Modifier =
|
|||||||
textColor = MaterialTheme.colorScheme.background
|
textColor = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
LazyRow(modifier = Modifier
|
LazyRow(modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(12.dp)
|
.padding(12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
items(castAndCrew.value?.cast?.size ?: 0) { i ->
|
items(castAndCrew.value?.cast?.size ?: 0) { i ->
|
||||||
val castMember = castAndCrew.value!!.cast[i]
|
val castMember = castAndCrew.value!!.cast[i]
|
||||||
CastCrewCard(person = castMember)
|
CastCrewCard(appNavController = appNavController, person = castMember)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CastCrewCard(person: Person) {
|
private fun CastCrewCard(appNavController: NavController, person: Person) {
|
||||||
ImageTextCard(
|
ImageTextCard(
|
||||||
title = person.name,
|
title = person.name,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -337,7 +432,12 @@ private fun CastCrewCard(person: Person) {
|
|||||||
imageUrl = TmdbUtils.getFullPersonImagePath(person),
|
imageUrl = TmdbUtils.getFullPersonImagePath(person),
|
||||||
noDataImage = R.drawable.no_person_photo,
|
noDataImage = R.drawable.no_person_photo,
|
||||||
titleTextColor = MaterialTheme.colorScheme.onPrimary,
|
titleTextColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
subtitleTextColor = Color.Unspecified
|
subtitleTextColor = Color.Unspecified,
|
||||||
|
onItemClicked = {
|
||||||
|
appNavController.navigate(
|
||||||
|
"${MainNavItem.DetailView.route}/${MediaViewType.PERSON}/${person.id}"
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,4 +612,15 @@ private fun fetchVideos(id: Int, service: DetailService, videoResponse: MutableS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchPerson(id: Int, person: MutableState<DetailPerson?>) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val result = PeopleService().getPerson(id)
|
||||||
|
if (result.isSuccessful) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
person.value = result.body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,6 @@ package com.owenlejeune.tvtime.ui.screens
|
|||||||
|
|
||||||
enum class MediaViewType {
|
enum class MediaViewType {
|
||||||
MOVIE,
|
MOVIE,
|
||||||
TV
|
TV,
|
||||||
|
PERSON
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ fun MediaTab(appNavController: NavHostController, mediaType: MediaViewType) {
|
|||||||
val tabs = when (mediaType) {
|
val tabs = when (mediaType) {
|
||||||
MediaViewType.MOVIE -> MainTabNavItem.MovieItems
|
MediaViewType.MOVIE -> MainTabNavItem.MovieItems
|
||||||
MediaViewType.TV -> MainTabNavItem.TvItems
|
MediaViewType.TV -> MainTabNavItem.TvItems
|
||||||
|
else -> throw IllegalArgumentException("Media type given: ${mediaType}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
|
||||||
}
|
}
|
||||||
val pagerState = rememberPagerState()
|
val pagerState = rememberPagerState()
|
||||||
Tabs(tabs = tabs, pagerState = pagerState)
|
Tabs(tabs = tabs, pagerState = pagerState)
|
||||||
@@ -44,6 +45,7 @@ fun MediaTabContent(appNavController: NavHostController, mediaType: MediaViewTyp
|
|||||||
val service: HomePageService = when(mediaType) {
|
val service: HomePageService = when(mediaType) {
|
||||||
MediaViewType.MOVIE -> MoviesService()
|
MediaViewType.MOVIE -> MoviesService()
|
||||||
MediaViewType.TV -> TvService()
|
MediaViewType.TV -> TvService()
|
||||||
|
else -> throw IllegalArgumentException("Media type given: ${mediaType}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
|
||||||
}
|
}
|
||||||
PosterGrid(
|
PosterGrid(
|
||||||
fetchMedia = { mediaList ->
|
fetchMedia = { mediaList ->
|
||||||
|
|||||||
Reference in New Issue
Block a user