mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 04:32:43 -05:00
add images to profile details
This commit is contained in:
@@ -7,7 +7,6 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
|
||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
@@ -18,12 +17,6 @@ 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>
|
||||
|
||||
@@ -40,6 +33,6 @@ interface PeopleApi {
|
||||
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
|
||||
|
||||
@GET("trending/person/{time_window}")
|
||||
suspend fun trending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response<SearchResult<SearchResultPerson>>
|
||||
suspend fun getTrending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response<SearchResult<SearchResultPerson>>
|
||||
|
||||
}
|
||||
@@ -6,17 +6,12 @@ import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCast
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImage
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMedia
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
|
||||
import com.owenlejeune.tvtime.utils.types.TimeWindow
|
||||
import okhttp3.internal.notify
|
||||
import org.koin.core.component.KoinComponent
|
||||
import retrofit2.Response
|
||||
import java.util.Collections
|
||||
@@ -95,7 +90,7 @@ class PeopleService: KoinComponent {
|
||||
}
|
||||
|
||||
suspend fun getTrending(timeWindow: TimeWindow, page: Int): Response<SearchResult<SearchResultPerson>> {
|
||||
return service.trending(timeWindow.name.lowercase(), page)
|
||||
return service.getTrending(timeWindow.name.lowercase(), page)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.owenlejeune.tvtime.extensions.safeGetSerializable
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.screens.AboutScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.AccountScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.GalleryView
|
||||
import com.owenlejeune.tvtime.ui.screens.HomeScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.KeywordResultsScreen
|
||||
import com.owenlejeune.tvtime.ui.screens.KnownForScreen
|
||||
@@ -187,6 +188,18 @@ fun AppNavigationHost(
|
||||
|
||||
KnownForScreen(appNavController = appNavController, id = id)
|
||||
}
|
||||
composable(
|
||||
route = AppNavItem.GalleryView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"),
|
||||
arguments = listOf(
|
||||
navArgument(NavConstants.TYPE_KEY) { type = NavType.EnumType(MediaViewType::class.java) },
|
||||
navArgument(NavConstants.ID_KEY) { type = NavType.IntType }
|
||||
)
|
||||
) { navBackStackEntry ->
|
||||
val type = navBackStackEntry.arguments?.safeGetSerializable(NavConstants.TYPE_KEY, MediaViewType::class.java)!!
|
||||
val id = navBackStackEntry.arguments?.getInt(NavConstants.ID_KEY)!!
|
||||
|
||||
GalleryView(id = id, type = type, appNavController = appNavController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,5 +230,8 @@ sealed class AppNavItem(val route: String) {
|
||||
object KnownForView: AppNavItem("known_for_route") {
|
||||
fun withArgs(id: Int) = route.plus("/$id")
|
||||
}
|
||||
object GalleryView: AppNavItem("gallery_view_route") {
|
||||
fun withArgs(type: MediaViewType, id: Int) = route.plus("/$type/$id")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GalleryView(
|
||||
id: Int,
|
||||
type: MediaViewType,
|
||||
appNavController: NavController
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
||||
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
||||
|
||||
val topAppBarScrollState = rememberTopAppBarState()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = TopAppBarDefaults
|
||||
.topAppBarColors(
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
title = { Text(text = "Images") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { appNavController.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(id = R.string.content_description_back_button),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
when (type) {
|
||||
MediaViewType.PERSON -> PersonGalleryView(id = id)
|
||||
MediaViewType.TV,
|
||||
MediaViewType.MOVIE -> MediaGalleryView(id = id, type = type)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediaGalleryView(
|
||||
id: Int,
|
||||
type: MediaViewType
|
||||
) {
|
||||
// todo - add support for images
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PersonGalleryView(
|
||||
id: Int
|
||||
) {
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
|
||||
val imagesMap = remember { mainViewModel.peopleImagesMap }
|
||||
val images = imagesMap[id] ?: emptyList()
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 120.dp),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
items(images) { image ->
|
||||
PosterItem(
|
||||
width = 120.dp,
|
||||
url = TmdbUtils.getFullPersonImagePath(image.filePath),
|
||||
placeholder = Icons.Filled.Person,
|
||||
title = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,18 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -42,6 +45,7 @@ import com.owenlejeune.tvtime.ui.components.ContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.ExternalIdsArea
|
||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||
@@ -62,6 +66,7 @@ fun PersonDetailScreen(
|
||||
mainViewModel.getById(personId, MediaViewType.PERSON)
|
||||
mainViewModel.getExternalIds(personId, MediaViewType.PERSON)
|
||||
mainViewModel.getCastAndCrew(personId, MediaViewType.PERSON)
|
||||
mainViewModel.getImages(personId, MediaViewType.PERSON)
|
||||
}
|
||||
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@@ -122,50 +127,9 @@ fun PersonDetailScreen(
|
||||
|
||||
BiographyCard(person = person)
|
||||
|
||||
val creditsMap = remember { mainViewModel.peopleCastMap }
|
||||
val credits = creditsMap[personId] ?: emptyList()
|
||||
|
||||
ContentCard(
|
||||
title = stringResource(R.string.known_for_label)
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
items(min(credits.size, 15)) { i ->
|
||||
val content = credits[i]
|
||||
|
||||
TwoLineImageTextCard(
|
||||
title = content.title,
|
||||
titleTextColor = MaterialTheme.colorScheme.primary,
|
||||
subtitle = content.character,
|
||||
modifier = Modifier
|
||||
.width(124.dp)
|
||||
.wrapContentHeight(),
|
||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||
onItemClicked = {
|
||||
appNavController.navigate(
|
||||
AppNavItem.DetailView.withArgs(content.mediaType, content.id)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.expand_see_all),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
.clickable {
|
||||
appNavController.navigate(AppNavItem.KnownForView.withArgs(personId))
|
||||
}
|
||||
)
|
||||
}
|
||||
CreditsCard(personId = personId, appNavController = appNavController)
|
||||
|
||||
ImagesCard(id = personId, appNavController = appNavController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,4 +152,111 @@ private fun BiographyCard(person: DetailPerson?) {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreditsCard(
|
||||
personId: Int,
|
||||
appNavController: NavController
|
||||
) {
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
|
||||
val creditsMap = remember { mainViewModel.peopleCastMap }
|
||||
val credits = creditsMap[personId] ?: emptyList()
|
||||
|
||||
ContentCard(
|
||||
title = stringResource(R.string.known_for_label)
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
items(min(credits.size, 15)) { i ->
|
||||
val content = credits[i]
|
||||
|
||||
TwoLineImageTextCard(
|
||||
title = content.title,
|
||||
titleTextColor = MaterialTheme.colorScheme.primary,
|
||||
subtitle = content.character,
|
||||
modifier = Modifier
|
||||
.width(124.dp)
|
||||
.wrapContentHeight(),
|
||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||
onItemClicked = {
|
||||
appNavController.navigate(
|
||||
AppNavItem.DetailView.withArgs(content.mediaType, content.id)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.expand_see_all),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
.clickable {
|
||||
appNavController.navigate(AppNavItem.KnownForView.withArgs(personId))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ImagesCard(
|
||||
id: Int,
|
||||
appNavController: NavController
|
||||
) {
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
val imagesMap = remember { mainViewModel.peopleImagesMap }
|
||||
val images = imagesMap[id] ?: emptyList()
|
||||
|
||||
ContentCard(
|
||||
title = stringResource(R.string.images_title)
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
items(images) { image ->
|
||||
PosterItem(
|
||||
width = 120.dp,
|
||||
url = TmdbUtils.getFullPersonImagePath(image.filePath),
|
||||
placeholder = Icons.Filled.Person,
|
||||
title = ""
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.expand_see_all),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
.clickable {
|
||||
appNavController.navigate(AppNavItem.GalleryView.withArgs(MediaViewType.PERSON, id))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -237,4 +237,5 @@
|
||||
<string name="cast_character_template">as %1$s</string>
|
||||
<string name="cast_tv_character_template">as %1$s (%2$d eps.)</string>
|
||||
<string name="crew_template">… %1$s</string>
|
||||
<string name="images_title">Images</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user