add popular people tab to home screen

This commit is contained in:
Owen LeJeune
2022-03-04 15:19:30 -05:00
parent 031e00734a
commit 4f664bb1a3
15 changed files with 205 additions and 53 deletions

View File

@@ -54,7 +54,7 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
}
override suspend fun postRating(id: Int, rating: RatingBody): Response<RatingResponse> {
val session = SessionManager.currentSession
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (session.isGuest) {
movieService.postMovieRatingAsGuest(id, session.sessionId, rating)
} else {
@@ -63,7 +63,7 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
}
override suspend fun deleteRating(id: Int): Response<RatingResponse> {
val session = SessionManager.currentSession
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (session.isGuest) {
movieService.deleteMovieReviewAsGuest(id, session.sessionId)
} else {

View File

@@ -54,7 +54,7 @@ class TvService: KoinComponent, DetailService, HomePageService {
}
override suspend fun postRating(id: Int, rating: RatingBody): Response<RatingResponse> {
val session = SessionManager.currentSession
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (session.isGuest) {
service.postTvRatingAsGuest(id, session.sessionId, rating)
} else {
@@ -63,7 +63,7 @@ class TvService: KoinComponent, DetailService, HomePageService {
}
override suspend fun deleteRating(id: Int): Response<RatingResponse> {
val session = SessionManager.currentSession
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (session.isGuest) {
service.deleteTvReviewAsGuest(id, session.sessionId)
} else {

View File

@@ -27,6 +27,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.model.HomePagePerson
import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.model.Person
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
@@ -61,6 +62,34 @@ fun PosterGrid(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PeoplePosterGrid(
fetchPeople: (MutableState<List<HomePagePerson>>) -> Unit = {},
onClick: (Int) -> Unit = {}
) {
val peopleList = remember { mutableStateOf(emptyList<HomePagePerson>()) }
fetchPeople(peopleList)
LazyVerticalGrid(
cells = GridCells.Adaptive(minSize = POSTER_WIDTH),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
listItems(peopleList.value) { person ->
PosterItem(
url = TmdbUtils.getFullPersonImagePath(person.profilePath),
noDataImage = R.drawable.no_person_photo,
modifier = Modifier.padding(5.dp),
onClick = {
onClick(person.id)
},
contentDescription = person.name
)
}
}
}
@Composable
fun PosterItem(
modifier: Modifier = Modifier,

View File

@@ -19,9 +19,9 @@ sealed class AccountTabNavItem(stringRes: Int, route: String, val mediaType: Med
val GuestItems = listOf(RatedMovies, RatedTvShows)//, RatedTvEpisodes)
}
object RatedMovies: AccountTabNavItem(R.string.nav_rated_movies_title, "rated_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession.ratedMovies } )
object RatedTvShows: AccountTabNavItem(R.string.nav_rated_shows_title, "rated_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession.ratedTvShows } )
object RatedTvEpisodes: AccountTabNavItem(R.string.nav_rated_episodes_title, "rated_episodes_route", MediaViewType.EPISODE, screenContent, { SessionManager.currentSession.ratedTvEpisodes } )
object RatedMovies: AccountTabNavItem(R.string.nav_rated_movies_title, "rated_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.ratedMovies ?: emptyList() } )
object RatedTvShows: AccountTabNavItem(R.string.nav_rated_shows_title, "rated_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.ratedTvShows ?: emptyList() } )
object RatedTvEpisodes: AccountTabNavItem(R.string.nav_rated_episodes_title, "rated_episodes_route", MediaViewType.EPISODE, screenContent, { SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() } )
}
private val screenContent: AccountNavComposableFun = { appNavController, mediaViewType, listFetchFun ->

View File

@@ -12,7 +12,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko
val name = resourceUtils.getString(stringRes)
companion object {
val Items = listOf(Movies, TV, Account, Settings)
val Items = listOf(Movies, TV, People, Account, Settings)
fun getByRoute(route: String?): BottomNavItem? {
return when (route) {
@@ -31,6 +31,6 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko
object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route")
object Favourites: BottomNavItem(R.string.nav_favourites_title, R.drawable.ic_favorite, "favourites_route")
object Settings: BottomNavItem(R.string.nav_settings_title, R.drawable.ic_settings, "settings_route")
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_person, "people_route")
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route")
}

View File

@@ -12,10 +12,7 @@ import com.owenlejeune.tvtime.ui.screens.MediaDetailView
import com.owenlejeune.tvtime.ui.screens.MainAppView
import com.owenlejeune.tvtime.ui.screens.MediaViewType
import com.owenlejeune.tvtime.ui.screens.PersonDetailView
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.AccountTab
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.SettingsTab
import com.owenlejeune.tvtime.ui.screens.tabs.bottom.*
object NavConstants {
const val ID_KEY = "id_key"
@@ -71,6 +68,9 @@ fun BottomNavigationRoutes(
composable(BottomNavItem.Account.route) {
AccountTab(appBarTitle = appBarTitle, appNavController = appNavController)
}
composable(BottomNavItem.People.route) {
PeopleTab(appBarTitle, appNavController = appNavController)
}
composable(BottomNavItem.Favourites.route) {
FavouritesTab()
}

View File

@@ -48,7 +48,7 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
val focusRequester = remember { FocusRequester() }
val focusSearchBar = remember { mutableStateOf(false) }
val searchableScreens = listOf(BottomNavItem.Movies.route, BottomNavItem.TV.route)
val searchableScreens = listOf(BottomNavItem.Movies.route, BottomNavItem.TV.route, BottomNavItem.People.route)
// todo - scroll state not remember when returing from detail screen

View File

@@ -205,7 +205,7 @@ private fun ActionsView(
service = service
)
if (!session.isGuest) {
if (session?.isGuest == false) {
ActionButton(
modifier = Modifier.weight(1f),
text = stringResource(R.string.add_to_list_action_label),
@@ -246,20 +246,25 @@ private fun RateButton(
var itemIsRated by remember {
mutableStateOf(
if (type == MediaViewType.MOVIE) {
session.hasRatedMovie(itemId)
session?.hasRatedMovie(itemId) == true
} else {
session.hasRatedTvShow(itemId)
session?.hasRatedTvShow(itemId) == true
}
)
}
val showRatingDialog = remember { mutableStateOf(false) }
val showSessionDialog = remember { mutableStateOf(false) }
ActionButton(
modifier = modifier,
text = if (itemIsRated) stringResource(R.string.delete_rating_action_label) else stringResource(R.string.rate_action_label),
onClick = {
if (!itemIsRated) {
showRatingDialog.value = true
if (SessionManager.currentSession != null) {
showRatingDialog.value = true
} else {
showSessionDialog.value = true
}
} else {
CoroutineScope(Dispatchers.IO).launch {
val response = service.deleteRating(itemId)
@@ -268,7 +273,7 @@ private fun RateButton(
itemIsRated = false
}
}
SessionManager.currentSession.refresh()
SessionManager.currentSession?.refresh()
}
}
}
@@ -277,7 +282,7 @@ private fun RateButton(
CoroutineScope(Dispatchers.IO).launch {
val response = service.postRating(itemId, RatingBody(rating = rating))
if (response.isSuccessful) {
SessionManager.currentSession.refresh()
SessionManager.currentSession?.refresh()
withContext(Dispatchers.Main) {
itemIsRated = true
}
@@ -289,6 +294,33 @@ private fun RateButton(
}
}
})
CreateSessionDialog(showDialog = showSessionDialog, onSessionReturned = {
})
}
@Composable
private fun CreateSessionDialog(showDialog: MutableState<Boolean>, onSessionReturned: (Boolean) -> Unit) {
if (showDialog.value) {
AlertDialog(
modifier = Modifier.wrapContentHeight(),
onDismissRequest = { showDialog.value = false },
title = { Text(text = "Sign In") },
confirmButton = {},
dismissButton = {
Button(
modifier = Modifier.height(40.dp),
onClick = {
showDialog.value = false
}
) {
Text(stringResource(R.string.action_cancel))
}
},
text = {}
)
}
}
@Composable
@@ -582,7 +614,7 @@ private fun ReviewsCard(
)
},
footer = {
if (!SessionManager.currentSession.isGuest) {
if (SessionManager.currentSession?.isGuest == false) {
Row(
modifier = Modifier
.fillMaxWidth()

View File

@@ -102,16 +102,16 @@ fun PersonDetailView(
}
}
ContentCard(title = stringResource(R.string.also_known_for_label)) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
if (departments.isNotEmpty()) {
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
if (departments.isNotEmpty()) {
ContentCard(title = stringResource(R.string.also_known_for_label)) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
departments.forEach { department ->
Text(text = department, color = MaterialTheme.colorScheme.primary)
LazyRow(
@@ -120,7 +120,8 @@ fun PersonDetailView(
.wrapContentHeight(),
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
val jobsInDepartment = credits.value!!.crew.filter { it.department == department }
val jobsInDepartment =
credits.value!!.crew.filter { it.department == department }
items(jobsInDepartment.size) { i ->
val content = jobsInDepartment[i]
val title = if (content.mediaType == MediaViewType.MOVIE) {

View File

@@ -31,24 +31,28 @@ import com.owenlejeune.tvtime.utils.TmdbUtils
@OptIn(ExperimentalPagerApi::class)
@Composable
fun AccountTab(appNavController: NavHostController, appBarTitle: MutableState<String>) {
if (SessionManager.currentSession.isGuest) {
if (SessionManager.currentSession?.isGuest == true) {
appBarTitle.value = "Hello, Guest"
}
val tabs = if (SessionManager.currentSession.isGuest) {
AccountTabNavItem.GuestItems
} else {
AccountTabNavItem.GuestItems
appBarTitle.value = "Not logged in"
}
Column {
val pagerState = rememberPagerState()
Tabs(tabs = tabs, pagerState = pagerState)
AccountTabs(
appNavController = appNavController,
tabs = tabs,
pagerState = pagerState
)
SessionManager.currentSession?.let { session ->
val tabs = if (session.isGuest) {
AccountTabNavItem.GuestItems
} else {
AccountTabNavItem.GuestItems
}
Column {
val pagerState = rememberPagerState()
Tabs(tabs = tabs, pagerState = pagerState)
AccountTabs(
appNavController = appNavController,
tabs = tabs,
pagerState = pagerState
)
}
}
}

View File

@@ -0,0 +1,58 @@
package com.owenlejeune.tvtime.ui.screens.tabs.bottom
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.PeopleService
import com.owenlejeune.tvtime.ui.components.PeoplePosterGrid
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.screens.MediaViewType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun PeopleTab(
appBarTitle: MutableState<String>,
appNavController: NavHostController
) {
// appBarTitle.value = stringResource(id = R.string.nav_popular_people_title)
val service = PeopleService()
Column {
Text(
modifier = Modifier.padding(start = 16.dp),
text = stringResource(id = R.string.nav_popular_people_title),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.headlineLarge
)
PeoplePosterGrid(
fetchPeople = { peopleList ->
CoroutineScope(Dispatchers.IO).launch {
val response = service.getPopular()
if (response.isSuccessful) {
withContext(Dispatchers.Main) {
peopleList.value = response.body()?.results ?: emptyList()
}
}
}
},
onClick = { id ->
appNavController.navigate(
"${MainNavItem.DetailView.route}/${MediaViewType.PERSON}/${id}"
)
}
)
}
}

View File

@@ -22,8 +22,10 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import com.owenlejeune.tvtime.BuildConfig
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.di.modules.preferencesModule
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.*
import com.owenlejeune.tvtime.utils.SessionManager
import org.koin.java.KoinJavaComponent.get
@Composable
@@ -90,7 +92,7 @@ fun SettingsTab(preferences: AppPreferences = get(AppPreferences::class.java)) {
}
@Composable
private fun DebugOptions() {
private fun DebugOptions(preferences: AppPreferences = get(AppPreferences::class.java)) {
val shouldShowPalette = remember { mutableStateOf(false) }
Text(
text = "Show material palette",
@@ -106,6 +108,19 @@ private fun DebugOptions() {
if (shouldShowPalette.value) {
PaletteDialog(shouldShowPalette)
}
Text(
text = "Clear session",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.clickable(
onClick = {
preferences.guestSessionId = ""
SessionManager.clearSession()
}
)
)
}
@Composable

View File

@@ -17,18 +17,20 @@ object SessionManager: KoinComponent {
private val preferences: AppPreferences by inject()
private var _currentSession: Session? = null
val currentSession: Session
get() = _currentSession!!
val currentSession: Session?
get() = _currentSession
private val authenticationService by lazy { TmdbClient().createAuthenticationService() }
fun clearSession() {
_currentSession = null
}
suspend fun initialize() {
_currentSession = if (preferences.guestSessionId.isNotEmpty()) {
if (preferences.guestSessionId.isNotEmpty()) {
val session = GuestSession()
session.initialize()
session
} else {
requestNewGuestSession()
_currentSession = session
}
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM15,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8 0,-0.29 0.02,-0.58 0.05,-0.86 2.36,-1.05 4.23,-2.98 5.21,-5.37C11.07,8.33 14.05,10 17.42,10c0.78,0 1.53,-0.09 2.25,-0.26 0.21,0.71 0.33,1.47 0.33,2.26 0,4.41 -3.59,8 -8,8z"/>
</vector>

View File

@@ -17,6 +17,7 @@
<string name="nav_rated_shows_title">Rated TV Shows</string>
<string name="nav_rated_episodes_title">Rated TV Episodes</string>
<string name="nav_people_title">People</string>
<string name="nav_popular_people_title">Popular People</string>
<!-- Headings -->
<string name="cast_label">Cast</string>