fix some sign in issues

This commit is contained in:
Owen LeJeune
2023-06-01 00:35:29 -04:00
parent 9a7297281e
commit 5fdb23ce42
15 changed files with 192 additions and 277 deletions

View File

@@ -1,7 +1,6 @@
package com.owenlejeune.tvtime
import android.os.Bundle
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.layout.*
@@ -59,7 +58,7 @@ class MainActivity : MonetCompatActivity() {
var mainNavStartRoute = BottomNavItem.SortedItems[0].route
intent.data?.let {
when (it.host) {
getString(R.string.intent_route_auth_return) -> mainNavStartRoute = BottomNavItem.Account.route
getString(R.string.intent_route_auth_return) -> mainNavStartRoute = BottomNavItem.SIGN_IN_PART_2_ROUTE
}
}
@@ -163,8 +162,8 @@ class MainActivity : MonetCompatActivity() {
scrolledContainerColor = MaterialTheme.colorScheme.background
),
actions = {
appBarActions.value(this)
defaultAppBarActions()
appBarActions.value(this)
}
)
}
@@ -180,6 +179,7 @@ class MainActivity : MonetCompatActivity() {
NavigationBar {
BottomNavItem.SortedItems.forEach { item ->
val isSelected = currentRoute == item.route || item.alternateRoutes.contains(currentRoute ?: "")
NavigationBarItem(
modifier = Modifier
.padding(4.dp)
@@ -189,13 +189,15 @@ class MainActivity : MonetCompatActivity() {
val name = if (preferences.showBottomTabLabels) item.name else " "
Text(text = name)
},
selected = currentRoute == item.route,
selected = isSelected,
onClick = {
onBottomAppBarItemClicked(
navController = navController,
appBarTitle = appBarTitle,
item = item
)
if (!isSelected) {
onBottomAppBarItemClicked(
navController = navController,
appBarTitle = appBarTitle,
item = item
)
}
}
)
}
@@ -293,16 +295,19 @@ class MainActivity : MonetCompatActivity() {
NavigationRail {
Spacer(modifier = Modifier.weight(1f))
BottomNavItem.SortedItems.forEachIndexed { index, item ->
val isSelected = currentRoute == item.route || item.alternateRoutes.contains(currentRoute ?: "")
NavigationRailItem(
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
label = { if (preferences.showBottomTabLabels) Text(item.name) },
selected = currentRoute == item.route,
selected = isSelected,
onClick = {
onBottomAppBarItemClicked(
navController = navController,
appBarTitle = appBarTitle,
item = item
)
if (!isSelected) {
onBottomAppBarItemClicked(
navController = navController,
appBarTitle = appBarTitle,
item = item
)
}
}
)
if (index < BottomNavItem.SortedItems.size - 1) {

View File

@@ -107,20 +107,16 @@ class TmdbClient: KoinComponent {
private fun sessionIdParam(urlSegments: List<String>): QueryParam? {
var sessionIdParam: QueryParam? = null
if (urlSegments.size > 1 && urlSegments[1] == "account") {
if (SessionManager.currentSession?.isAuthorized == true) {
sessionIdParam = QueryParam("session_id", SessionManager.currentSession!!.sessionId)
} else if (preferences.authorizedSessionId.isNotEmpty()) {
sessionIdParam = QueryParam("session_id", preferences.authorizedSessionId)
} else if (preferences.authorizedSessionValues != null) {
val sessionId = preferences.authorizedSessionValues!!.sessionId
sessionIdParam = QueryParam("session_id", sessionId)
val currentSession = SessionManager.currentSession.value
if (!currentSession?.sessionId.isNullOrEmpty()) {
sessionIdParam = QueryParam("session_id", currentSession!!.sessionId)
}
}
return sessionIdParam
}
private fun shouldIncludeLanguageParam(urlSegments: List<String>): Boolean {
val ignoredRoutes = listOf("images")
val ignoredRoutes = listOf("images", "account")
for (route in ignoredRoutes) {
if (urlSegments.contains(route)) {
return false
@@ -147,7 +143,7 @@ class TmdbClient: KoinComponent {
if (url.encodedPathSegments.contains("auth")) {
builder.header("Authorization", "Bearer ${BuildConfig.TMDB_Api_v4Key}")
} else {
builder.header("Authorization", "Bearer ${SessionManager.currentSession!!.accessToken}")
builder.header("Authorization", "Bearer ${SessionManager.currentSession.value!!.accessToken}")
}
if (shouldIncludeLanguageParam(url.encodedPathSegments)) {
val locale = Locale.current

View File

@@ -42,13 +42,6 @@ interface MoviesApi {
@GET("movie/{id}/keywords")
suspend fun getKeywords(@Path("id") id: Int): Response<KeywordsResponse>
@POST("movie/{id}/rating")
suspend fun postMovieRatingAsGuest(
@Path("id") id: Int,
@Query("guest_session_id") guestSessionId: String,
@Body ratingBody: RatingBody
): Response<StatusResponse>
@POST("movie/{id}/rating")
suspend fun postMovieRatingAsUser(
@Path("id") id: Int,
@@ -56,12 +49,6 @@ interface MoviesApi {
@Body ratingBody: RatingBody
): Response<StatusResponse>
@DELETE("movie/{id}/rating")
suspend fun deleteMovieReviewAsGuest(
@Path("id") id: Int,
@Query("guest_session_id") guestSessionId: String
): Response<StatusResponse>
@DELETE("movie/{id}/rating")
suspend fun deleteMovieReviewAsUser(
@Path("id") id: Int,

View File

@@ -1,7 +1,16 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastAndCrew
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieReleaseResults
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
import com.owenlejeune.tvtime.utils.SessionManager
import org.koin.core.component.KoinComponent
import retrofit2.Response
@@ -55,21 +64,13 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
}
override suspend fun postRating(id: Int, rating: RatingBody): Response<StatusResponse> {
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (!session.isAuthorized) {
movieService.postMovieRatingAsGuest(id, session.sessionId, rating)
} else {
movieService.postMovieRatingAsUser(id, session.sessionId, rating)
}
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
return movieService.postMovieRatingAsUser(id, session.sessionId, rating)
}
override suspend fun deleteRating(id: Int): Response<StatusResponse> {
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (!session.isAuthorized) {
movieService.deleteMovieReviewAsGuest(id, session.sessionId)
} else {
movieService.deleteMovieReviewAsUser(id, session.sessionId)
}
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
return movieService.deleteMovieReviewAsUser(id, session.sessionId)
}
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {

View File

@@ -42,13 +42,6 @@ interface TvApi {
@GET("tv/{id}/keywords")
suspend fun getKeywords(@Path("id") id: Int): Response<KeywordsResponse>
@POST("tv/{id}/rating")
suspend fun postTvRatingAsGuest(
@Path("id") id: Int,
@Query("guest_session_id") guestSessionId: String,
@Body ratingBody: RatingBody
): Response<StatusResponse>
@POST("tv/{id}/rating")
suspend fun postTvRatingAsUser(
@Path("id") id: Int,
@@ -56,12 +49,6 @@ interface TvApi {
@Body ratingBody: RatingBody
): Response<StatusResponse>
@DELETE("tv/{id}/rating")
suspend fun deleteTvReviewAsGuest(
@Path("id") id: Int,
@Query("guest_session_id") guestSessionId: String
): Response<StatusResponse>
@DELETE("tv/{id}/rating")
suspend fun deleteTvReviewAsUser(
@Path("id") id: Int,

View File

@@ -1,7 +1,16 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastAndCrew
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
import com.owenlejeune.tvtime.utils.SessionManager
import org.koin.core.component.KoinComponent
import retrofit2.Response
@@ -54,22 +63,14 @@ class TvService: KoinComponent, DetailService, HomePageService {
return service.getReviews(id)
}
override suspend fun postRating(id: Int, rating: RatingBody): Response<StatusResponse> {
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (!session.isAuthorized) {
service.postTvRatingAsGuest(id, session.sessionId, rating)
} else {
service.postTvRatingAsUser(id, session.sessionId, rating)
}
override suspend fun postRating(id: Int, ratingBody: RatingBody): Response<StatusResponse> {
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
return service.postTvRatingAsUser(id, session.sessionId, ratingBody)
}
override suspend fun deleteRating(id: Int): Response<StatusResponse> {
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
return if (!session.isAuthorized) {
service.deleteTvReviewAsGuest(id, session.sessionId)
} else {
service.deleteTvReviewAsUser(id, session.sessionId)
}
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
return service.deleteTvReviewAsUser(id, session.sessionId)
}
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {

View File

@@ -18,15 +18,12 @@ class AppPreferences(context: Context) {
private val PREF_FILE = "tvtime_shared_preferences"
private val PERSISTENT_SEARCH = "persistent_search"
private val GUEST_SESSION = "guest_session_id"
private val AUTHORIZED_SESSION = "authorized_session_id"
private val AUTHORIZED_SESSION_VALUES = "authorized_session_values"
private val FIRST_LAUNCH = "first_launch"
private val FIRST_LAUNCH_TESTING = "first_launch_testing"
private val CHROMA_MULTIPLIER = "chroma_multiplier"
private val USE_SYSTEM_COLORS = "use_system_colors"
private val SELECTED_COLOR = "selected_color"
private val USE_V4_API = "use_v4_api"
private val SHOW_BACKDROP_GALLERY = "show_backdrop_gallery"
private val USE_WALLPAPER_COLORS = "use_wallpaper_colors"
private val DARK_THEME = "dark_theme"
@@ -79,20 +76,12 @@ class AppPreferences(context: Context) {
set(value) { preferences.put(SELECTED_COLOR, value) }
/******* Session Tokens ********/
var guestSessionId: String
get() = preferences.getString(GUEST_SESSION, "") ?: ""
set(value) { preferences.put(GUEST_SESSION, value) }
var authorizedSessionValues: SessionManager.AuthorizedSessionValues?
get() = preferences.getString(AUTHORIZED_SESSION_VALUES, null)?.let {
Gson().fromJson(it, SessionManager.AuthorizedSessionValues::class.java)
}
set(value) { preferences.putNullableString(AUTHORIZED_SESSION_VALUES, value?.let { Gson().toJson(value) }) }
var authorizedSessionId: String
get() = preferences.getString(AUTHORIZED_SESSION, "") ?: ""
set(value) { preferences.put(AUTHORIZED_SESSION, value) }
/******** Home Screen Preferences ********/
val moviesTabPositionDefault: Int = 0
var moviesTabPosition: Int
@@ -134,11 +123,6 @@ class AppPreferences(context: Context) {
get() = preferences.getBoolean(FIRST_LAUNCH, true)
set(value) { preferences.put(FIRST_LAUNCH, value) }
val useV4ApiDefault: Boolean = true
var useV4Api: Boolean
get() = preferences.getBoolean(USE_V4_API, useV4ApiDefault)
set(value) { preferences.put(USE_V4_API, value) }
val showBackdropGalleryDefault: Boolean = true
var showBackdropGallery: Boolean
get() = preferences.getBoolean(SHOW_BACKDROP_GALLERY, showBackdropGalleryDefault)

View File

@@ -3,10 +3,16 @@ package com.owenlejeune.tvtime.ui.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedEpisode
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedTv
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.AccountTabContent
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.RecommendedAccountTabContent
import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.SessionManager
@@ -45,7 +51,7 @@ sealed class AccountTabNavItem(
R.string.no_rated_movies,
MediaViewType.MOVIE,
screenContent,
{ SessionManager.currentSession?.ratedMovies ?: emptyList() },
{ SessionManager.currentSession.value?.ratedMovies ?: emptyList() },
RatedMovie::class,
0
)
@@ -55,7 +61,7 @@ sealed class AccountTabNavItem(
R.string.no_rated_tv,
MediaViewType.TV,
screenContent,
{ SessionManager.currentSession?.ratedTvShows ?: emptyList() },
{ SessionManager.currentSession.value?.ratedTvShows ?: emptyList() },
RatedTv::class,
1
)
@@ -65,7 +71,7 @@ sealed class AccountTabNavItem(
R.string.no_rated_episodes,
MediaViewType.EPISODE,
screenContent,
{ SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() },
{ SessionManager.currentSession.value?.ratedTvEpisodes ?: emptyList() },
RatedEpisode::class,
-1 //2
)
@@ -75,7 +81,7 @@ sealed class AccountTabNavItem(
R.string.no_favorite_movies,
MediaViewType.MOVIE,
screenContent,
{ SessionManager.currentSession?.favoriteMovies ?: emptyList() },
{ SessionManager.currentSession.value?.favoriteMovies ?: emptyList() },
FavoriteMovie::class,
3
)
@@ -85,7 +91,7 @@ sealed class AccountTabNavItem(
R.string.no_favorite_tv,
MediaViewType.TV,
screenContent,
{ SessionManager.currentSession?.favoriteTvShows ?: emptyList() },
{ SessionManager.currentSession.value?.favoriteTvShows ?: emptyList() },
FavoriteTvSeries::class,
4
)
@@ -95,7 +101,7 @@ sealed class AccountTabNavItem(
R.string.no_watchlist_movies,
MediaViewType.MOVIE,
screenContent,
{ SessionManager.currentSession?.movieWatchlist ?: emptyList() },
{ SessionManager.currentSession.value?.movieWatchlist ?: emptyList() },
WatchlistMovie::class,
5
)
@@ -105,7 +111,7 @@ sealed class AccountTabNavItem(
R.string.no_watchlist_tv,
MediaViewType.TV,
screenContent,
{ SessionManager.currentSession?.tvWatchlist ?: emptyList() },
{ SessionManager.currentSession.value?.tvWatchlist ?: emptyList() },
WatchlistTvSeries::class,
6
)
@@ -116,7 +122,7 @@ sealed class AccountTabNavItem(
R.string.no_lists,
MediaViewType.LIST,
screenContent,
{ SessionManager.currentSession?.accountLists ?: emptyList() },
{ SessionManager.currentSession.value?.accountLists ?: emptyList() },
V4AccountList::class,
7
)

View File

@@ -11,7 +11,8 @@ sealed class BottomNavItem(
val icon: Int,
val route: String,
private val orderGetter: (AppPreferences) -> Int,
private val orderSetter: (AppPreferences, Int) -> Unit
private val orderSetter: (AppPreferences, Int) -> Unit,
val alternateRoutes: List<String> = emptyList()
): KoinComponent {
private val appPreferences: AppPreferences by inject()
@@ -23,6 +24,9 @@ sealed class BottomNavItem(
set(value) { orderSetter.invoke(appPreferences, value) }
companion object {
const val SIGN_IN_PART_2_ROUTE = "sign_in_part_two_route"
val SortedItems
get() = Items.filter { it.order > -1 }.sortedBy { it.order }.ifEmpty { Items }
@@ -42,7 +46,7 @@ sealed class BottomNavItem(
object Movies: BottomNavItem(R.string.nav_movies_title, R.drawable.ic_movie, "movies_route", { it.moviesTabPosition }, { p, i -> p.moviesTabPosition = i } )
object TV: BottomNavItem(R.string.nav_tv_title, R.drawable.ic_tv, "tv_route", { it.tvTabPosition }, { p, i -> p.tvTabPosition = i } )
object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route", { it.accountTabPosition }, { p, i -> p.accountTabPosition = i } )
object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route", { it.accountTabPosition }, { p, i -> p.accountTabPosition = i }, listOf(SIGN_IN_PART_2_ROUTE) )
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route", { it.peopleTabPosition }, { p, i -> p.peopleTabPosition = i } )
}

View File

@@ -6,8 +6,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.*
@@ -46,7 +48,20 @@ fun MainNavGraph(
)
}
composable(BottomNavItem.Account.route) {
AccountTab(appBarTitle = appBarTitle, appNavController = appNavController, appBarActions = appBarActions)
AccountTab(
appBarTitle = appBarTitle,
appNavController = appNavController,
appBarActions = appBarActions
)
fab.value = {}
}
composable(BottomNavItem.SIGN_IN_PART_2_ROUTE) {
AccountTab(
appBarTitle = appBarTitle,
appNavController = appNavController,
appBarActions = appBarActions,
doSignInPartTwo = true
)
fab.value = {}
}
composable(BottomNavItem.People.route) {

View File

@@ -31,7 +31,6 @@ import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.RoundedLetterImage
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
@@ -44,78 +43,54 @@ import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.get
import kotlin.reflect.KClass
private const val GUEST_SIGN_IN = "guest_sign_in"
private const val SIGN_OUT = "sign_out"
private const val NO_SESSION_SIGN_IN = "no_session_sign_in"
private const val NO_SESSION_SIGN_IN_GUEST = "no_session_sign_in_guest"
@OptIn(ExperimentalPagerApi::class)
@Composable
fun AccountTab(
appNavController: NavHostController,
appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
doSignInPartTwo: Boolean = false
) {
val lastSelectedOption = remember { mutableStateOf("") }
val currentSessionState = remember { SessionManager.currentSession }
val currentSession = currentSessionState.value
val lso = lastSelectedOption.value
if (SessionManager.isV4SignInInProgress) {
if (currentSession?.isAuthorized == false) {
appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
AccountLoadingView()
v4SignInPart2(lastSelectedOption)
if (doSignInPartTwo) {
AccountLoadingView()
signInPart2()
}
} else {
when (SessionManager.currentSession?.isAuthorized) {
false -> {
appBarTitle.value =
stringResource(
id = R.string.account_header_title_formatted,
stringResource(id = R.string.account_name_guest)
)
}
true -> {
appBarTitle.value =
stringResource(
id = R.string.account_header_title_formatted,
getAccountName(SessionManager.currentSession?.accountDetails)
)
}
else -> {
appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
}
if (currentSession?.isAuthorized == true) {
appBarTitle.value =
stringResource(
id = R.string.account_header_title_formatted,
getAccountName(currentSession.accountDetails)
)
} else {
appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
}
appBarActions.value = {
AccountDropdownMenu(
session = SessionManager.currentSession,
lastSelectedOption = lastSelectedOption
session = currentSession
)
}
if (!SessionManager.isV4SignInInProgress) {
SessionManager.currentSession?.let { session ->
val tabs = if (session.isAuthorized) {
AccountTabNavItem.AuthorizedItems
} else {
AccountTabNavItem.GuestItems
}
currentSession?.let {
Column {
AuthorizedSessionIcon()
Column {
if (session.isAuthorized) {
AuthorizedSessionIcon()
}
val pagerState = rememberPagerState()
ScrollableTabs(tabs = tabs, pagerState = pagerState)
AccountTabs(
appNavController = appNavController,
tabs = tabs,
pagerState = pagerState
)
}
val tabs = AccountTabNavItem.AuthorizedItems
val pagerState = rememberPagerState()
ScrollableTabs(tabs = tabs, pagerState = pagerState)
AccountTabs(
appNavController = appNavController,
tabs = tabs,
pagerState = pagerState
)
}
}
}
@@ -309,10 +284,7 @@ private fun MediaItemRow(
}
@Composable
private fun AccountDropdownMenu(
session: SessionManager.Session?,
lastSelectedOption: MutableState<String>
) {
private fun AccountDropdownMenu(session: SessionManager.Session?) {
val expanded = remember { mutableStateOf(false) }
IconButton(
@@ -325,73 +297,52 @@ private fun AccountDropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false }
) {
when(session?.isAuthorized) {
true -> { AuthorizedSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
// false -> { GuestSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
false -> {}
null -> { NoSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
if(session?.isAuthorized == true) {
AuthorizedSessionMenuItems(expanded = expanded)
} else {
NoSessionMenuItems(expanded = expanded)
}
}
}
@Composable
private fun AuthorizedSessionMenuItems(
expanded: MutableState<Boolean>,
lastSelectedOption: MutableState<String>,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
private fun AuthorizedSessionMenuItems(expanded: MutableState<Boolean>) {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_out)) },
onClick = {
if (preferences.useV4Api) {
signOutV4(lastSelectedOption)
} else {
// signOut(lastSelectedOption)
}
signOut()
expanded.value = false
}
)
}
@Composable
private fun NoSessionMenuItems(
expanded: MutableState<Boolean>,
lastSelectedOption: MutableState<String>,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
private fun NoSessionMenuItems(expanded: MutableState<Boolean>) {
val context = LocalContext.current
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in)) },
onClick = {
if (preferences.useV4Api) {
v4SignInPart1(context)
} else {
// showSignInDialog.value = true
}
signInPart1(context)
expanded.value = false
}
)
}
private fun v4SignInPart1(context: Context) {
private fun signInPart1(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.signInWithV4Part1(context)
SessionManager.signInPart1(context)
}
}
private fun v4SignInPart2(lastSelectedOption: MutableState<String>) {
private fun signInPart2() {
CoroutineScope(Dispatchers.IO).launch {
val signIn = SessionManager.signInWithV4Part2()
if (signIn) {
withContext(Dispatchers.Main) {
lastSelectedOption.value = NO_SESSION_SIGN_IN
}
}
SessionManager.singInPart2()
}
}
@Composable
private fun AuthorizedSessionIcon() {
val accountDetails = SessionManager.currentSession?.accountDetails
val accountDetails = SessionManager.currentSession.value?.accountDetails
val avatarUrl = accountDetails?.let {
when {
accountDetails.avatar.tmdb?.avatarPath?.isNotEmpty() == true -> {
@@ -423,13 +374,9 @@ private fun AuthorizedSessionIcon() {
}
}
private fun signOutV4(lastSelectedOption: MutableState<String>) {
private fun signOut() {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.clearSessionV4 { isSuccessful ->
if (isSuccessful) {
lastSelectedOption.value = SIGN_OUT
}
}
SessionManager.clearSession()
}
}

View File

@@ -544,7 +544,7 @@ private fun ListItemView(
@Composable
private fun ActionButtonRow(listItem: ListItem) {
val session = SessionManager.currentSession
val session = SessionManager.currentSession.value
val (isFavourited, isWatchlisted, isRated) = if (listItem.mediaType == MediaViewType.MOVIE) {
Triple(
@@ -606,11 +606,12 @@ private fun addToWatchlist(
itemIsWatchlisted: MutableState<Boolean>,
onWatchlistChanged: (Boolean) -> Unit
) {
val accountId = SessionManager.currentSession!!.accountDetails!!.id
val currentSession = SessionManager.currentSession.value
val accountId = currentSession!!.accountDetails!!.id
CoroutineScope(Dispatchers.IO).launch {
val response = AccountService().addToWatchlist(accountId, WatchlistBody(type, itemId, !itemIsWatchlisted.value))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Watchlist)
currentSession.refresh(changed = SessionManager.Session.Changed.Watchlist)
withContext(Dispatchers.Main) {
itemIsWatchlisted.value = !itemIsWatchlisted.value
onWatchlistChanged(itemIsWatchlisted.value)
@@ -630,11 +631,12 @@ private fun addToFavorite(
itemIsFavorited: MutableState<Boolean>,
onFavoriteChanged: (Boolean) -> Unit
) {
val accountId = SessionManager.currentSession!!.accountDetails!!.id
val currentSession = SessionManager.currentSession.value
val accountId = currentSession!!.accountDetails!!.id
CoroutineScope(Dispatchers.IO).launch {
val response = AccountService().markAsFavorite(accountId, MarkAsFavoriteBody(type, itemId, !itemIsFavorited.value))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Favorites)
currentSession.refresh(changed = SessionManager.Session.Changed.Favorites)
withContext(Dispatchers.Main) {
itemIsFavorited.value = !itemIsFavorited.value
onFavoriteChanged(itemIsFavorited.value)
@@ -682,7 +684,7 @@ private fun removeItemFromList(
val removeItem = DeleteListItemsItem(itemId, itemType)
val result = service.deleteListItems(listId, DeleteListItemsBody(listOf(removeItem)))
if (result.isSuccessful) {
SessionManager.currentSession?.refresh(SessionManager.Session.Changed.List)
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.List)
service.getList(listId).body()?.let {
withContext(Dispatchers.Main) {
list.value = it

View File

@@ -267,7 +267,7 @@ private fun ActionsView(
modifier: Modifier = Modifier
) {
itemId?.let {
val session = SessionManager.currentSession
val session = SessionManager.currentSession.value
Row(
modifier = modifier
.wrapContentSize(),
@@ -359,7 +359,7 @@ private fun RateButton(
service: DetailService,
modifier: Modifier = Modifier
) {
val session = SessionManager.currentSession
val session = SessionManager.currentSession.value
val context = LocalContext.current
val itemIsRated = remember {
@@ -432,7 +432,7 @@ fun WatchlistButton(
type: MediaViewType,
modifier: Modifier = Modifier
) {
val session = SessionManager.currentSession
val session = SessionManager.currentSession.value
val hasWatchlistedItem = if (type == MediaViewType.MOVIE) {
session?.hasWatchlistedMovie(itemId) == true
@@ -492,7 +492,7 @@ fun FavoriteButton(
type: MediaViewType,
modifier: Modifier = Modifier
) {
val session = SessionManager.currentSession
val session = SessionManager.currentSession.value
val isFavourited = if (type == MediaViewType.MOVIE) {
session?.hasFavoritedMovie(itemId) == true
} else {
@@ -993,7 +993,7 @@ private fun ReviewsCard(
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (SessionManager.currentSession?.isAuthorized == true) {
if (SessionManager.currentSession.value?.isAuthorized == true) {
Row(
modifier = Modifier
.fillMaxWidth()
@@ -1230,7 +1230,7 @@ private fun postRating(context: Context, rating: Float, itemId: Int, service: De
CoroutineScope(Dispatchers.IO).launch {
val response = service.postRating(itemId, RatingBody(rating = rating))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = arrayOf(SessionManager.Session.Changed.RatedMovies, SessionManager.Session.Changed.RatedTv))
SessionManager.currentSession.value?.refresh(changed = arrayOf(SessionManager.Session.Changed.RatedMovies, SessionManager.Session.Changed.RatedTv))
withContext(Dispatchers.Main) {
itemIsRated.value = true
}
@@ -1247,7 +1247,7 @@ private fun deleteRating(context: Context, itemId: Int, service: DetailService,
CoroutineScope(Dispatchers.IO).launch {
val response = service.deleteRating(itemId)
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Rated)
SessionManager.currentSession.value?.refresh(changed = SessionManager.Session.Changed.Rated)
withContext(Dispatchers.Main) {
itemIsRated.value = false
}
@@ -1267,11 +1267,12 @@ private fun addToWatchlist(
itemIsWatchlisted: MutableState<Boolean>,
onWatchlistChanged: (Boolean) -> Unit
) {
val accountId = SessionManager.currentSession!!.accountDetails!!.id
val currentSession = SessionManager.currentSession.value
val accountId = currentSession!!.accountDetails!!.id
CoroutineScope(Dispatchers.IO).launch {
val response = AccountService().addToWatchlist(accountId, WatchlistBody(type, itemId, !itemIsWatchlisted.value))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Watchlist)
currentSession.refresh(changed = SessionManager.Session.Changed.Watchlist)
withContext(Dispatchers.Main) {
itemIsWatchlisted.value = !itemIsWatchlisted.value
onWatchlistChanged(itemIsWatchlisted.value)
@@ -1291,11 +1292,12 @@ private fun addToFavorite(
itemIsFavorited: MutableState<Boolean>,
onFavoriteChanged: (Boolean) -> Unit
) {
val accountId = SessionManager.currentSession!!.accountDetails!!.id
val currentSession = SessionManager.currentSession.value
val accountId = currentSession!!.accountDetails!!.id
CoroutineScope(Dispatchers.IO).launch {
val response = AccountService().markAsFavorite(accountId, MarkAsFavoriteBody(type, itemId, !itemIsFavorited.value))
if (response.isSuccessful) {
SessionManager.currentSession?.refresh(changed = SessionManager.Session.Changed.Favorites)
currentSession.refresh(changed = SessionManager.Session.Changed.Favorites)
withContext(Dispatchers.Main) {
itemIsFavorited.value = !itemIsFavorited.value
onFavoriteChanged(itemIsFavorited.value)

View File

@@ -480,31 +480,12 @@ private fun DevPreferences(
.padding(horizontal = 8.dp, vertical = 12.dp)
.clickable(
onClick = {
preferences.guestSessionId = ""
coroutineScope.launch {
SessionManager.clearSessionV4 {
Toast
.makeText(
context,
"Cleared session v4: $it",
Toast.LENGTH_SHORT
)
.show()
}
SessionManager.clearSession()
}
}
)
)
val useV4Api = remember { mutableStateOf(preferences.useV4Api) }
SwitchPreference(
titleText = "Use v4 API",
checkState = useV4Api.value,
onCheckedChange = { isChecked ->
useV4Api.value = isChecked
preferences.useV4Api = isChecked
}
)
}
}
@@ -613,7 +594,6 @@ private fun resetDarkModePreferences(preferences: AppPreferences) {
private fun resetDevModePreference(preferences: AppPreferences) {
preferences.firstLaunchTesting = preferences.firstLaunchTestingDefault
preferences.useV4Api = preferences.useV4ApiDefault
preferences.showBackdropGallery = preferences.showBackdropGalleryDefault
}

View File

@@ -3,6 +3,8 @@ package com.owenlejeune.tvtime.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.runtime.mutableStateOf
import com.google.gson.annotations.SerializedName
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
@@ -22,16 +24,13 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.java.KoinJavaComponent.get
object SessionManager: KoinComponent {
private val preferences: AppPreferences by inject()
private var _currentSession: Session? = null
val currentSession: Session?
get() = _currentSession
var isV4SignInInProgress: Boolean = false
val currentSession = mutableStateOf<Session?>(null)
private val authenticationService by lazy { TmdbClient().createAuthenticationService() }
private val authenticationV4Service by lazy { TmdbClient().createV4AuthenticationService() }
@@ -42,17 +41,15 @@ object SessionManager: KoinComponent {
@SerializedName("account_id") val accountId: String
)
fun clearSessionV4(onResponse: (isSuccessful: Boolean) -> Unit) {
currentSession?.let { session ->
fun clearSession() {
currentSession.value?.let { session ->
CoroutineScope(Dispatchers.IO).launch {
val deleteResponse = authenticationV4Service.deleteAccessToken(AuthDeleteBody(session.accessToken))
withContext(Dispatchers.Main) {
if (deleteResponse.isSuccessful) {
_currentSession = null
preferences.guestSessionId = ""
currentSession.value = null
preferences.authorizedSessionValues = null
}
onResponse(deleteResponse.isSuccessful)
}
}
}
@@ -65,19 +62,17 @@ object SessionManager: KoinComponent {
accessToken = values.accessToken,
accountId = values.accountId
)
_currentSession = session
currentSession.value = session
session.initialize()
}
}
suspend fun signInWithV4Part1(context: Context) {
isV4SignInInProgress = true
suspend fun signInPart1(context: Context) {
val service = AuthenticationV4Service()
val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return"))
if (requestTokenResponse.isSuccessful) {
requestTokenResponse.body()?.let { ctr ->
_currentSession = InProgressSession(ctr.requestToken)
currentSession.value = InProgressSession(ctr.requestToken)
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse(
@@ -89,9 +84,11 @@ object SessionManager: KoinComponent {
}
}
suspend fun signInWithV4Part2(): Boolean {
if (isV4SignInInProgress && _currentSession is InProgressSession) {
val requestToken = _currentSession!!.sessionId
suspend fun singInPart2(
context: Context = get(Context::class.java)
) {
if (currentSession.value is InProgressSession) {
val requestToken = currentSession.value!!.sessionId
val authResponse = authenticationV4Service.createAccessToken(AuthAccessBody(requestToken))
if (authResponse.isSuccessful) {
authResponse.body()?.let { ar ->
@@ -104,23 +101,26 @@ object SessionManager: KoinComponent {
accountId = ar.accountId,
accessToken = ar.accessToken
)
preferences.authorizedSessionId = ""
preferences.guestSessionId = ""
_currentSession = AuthorizedSession(
val session = AuthorizedSession(
sessionId = sr.sessionId,
accessToken = ar.accessToken,
accountId = ar.accountId
)
_currentSession?.initialize()
isV4SignInInProgress = false
return true
currentSession.value = session
session.initialize()
}
}
} else {
currentSession.value = null
Toast.makeText(
context,
"Error signing in",
Toast.LENGTH_SHORT
).show()
}
}
}
}
return false
}
abstract class Session(val sessionId: String, val isAuthorized: Boolean, val accessToken: String = "", val accountId: String = "") {
@@ -217,7 +217,7 @@ object SessionManager: KoinComponent {
val Rated get() = arrayOf(RatedMovies, RatedTv, RatedEpisodes)
val Favorites get() = arrayOf(FavoriteMovies, FavoriteTv)
val Watchlist get() = arrayOf(WatchlistMovies, WatchlistTv)
val List get() = arrayOf(Changed.Lists)
val List get() = arrayOf(Lists)
}
}
}
@@ -234,7 +234,7 @@ object SessionManager: KoinComponent {
}
private class AuthorizedSession(
sessionId: String = preferences.authorizedSessionId,
sessionId: String = "",
accessToken: String = "",
accountId: String = ""
): Session(sessionId, true, accessToken, accountId) {
@@ -247,12 +247,11 @@ object SessionManager: KoinComponent {
override suspend fun refresh(changed: Array<Changed>) {
if (changed.contains(Changed.AccountDetails)) {
service.getAccountDetails().apply {
if (isSuccessful) {
_accountDetails = body() ?: _accountDetails
accountDetails?.let {
refreshWithAccountId(it.id, changed)
}
val response = service.getAccountDetails()
if (response.isSuccessful) {
_accountDetails = response.body() ?: _accountDetails
accountDetails?.let {
refreshWithAccountId(it.id, changed)
}
}
} else if (accountDetails != null) {
@@ -335,5 +334,4 @@ object SessionManager: KoinComponent {
}
}
}
}