diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleApi.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleApi.kt index 5b46e60..4ceb529 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleApi.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleApi.kt @@ -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 -// @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 @@ -40,6 +33,6 @@ interface PeopleApi { suspend fun getExternalIds(@Path("id") id: Int): Response @GET("trending/person/{time_window}") - suspend fun trending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response> + suspend fun getTrending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response> } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleService.kt index 0ca30e9..638e392 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleService.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/PeopleService.kt @@ -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> { - return service.trending(timeWindow.name.lowercase(), page) + return service.getTrending(timeWindow.name.lowercase(), page) } } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/AppNavigation.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/AppNavigation.kt index 25b4880..44ff381 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/AppNavigation.kt @@ -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") + } } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/GalleryScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/GalleryScreen.kt new file mode 100644 index 0000000..dec52f2 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/GalleryScreen.kt @@ -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() + + 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 = "" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/PeopleDetailScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/PeopleDetailScreen.kt index 826f656..03b8a84 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/PeopleDetailScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/PeopleDetailScreen.kt @@ -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() + + 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() + 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)) + } + ) + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f481c23..59dbacb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -237,4 +237,5 @@ as %1$s as %1$s (%2$d eps.) … %1$s + Images \ No newline at end of file