add images to profile details

This commit is contained in:
Owen LeJeune
2023-06-27 00:07:01 -04:00
parent ca878e3577
commit edad88a81d
6 changed files with 250 additions and 58 deletions

View File

@@ -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>>
}

View File

@@ -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)
}
}

View File

@@ -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")
}
}

View File

@@ -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 = ""
)
}
}
}

View File

@@ -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))
}
)
}
}

View File

@@ -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>