mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-13 23:32:46 -05:00
add popular people tab to home screen
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
app/src/main/res/drawable/ic_face.xml
Normal file
10
app/src/main/res/drawable/ic_face.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user