remove guest session code

This commit is contained in:
Owen LeJeune
2023-05-31 21:43:17 -04:00
parent d6f43b7579
commit 9a7297281e
7 changed files with 13 additions and 462 deletions

View File

@@ -9,21 +9,6 @@ import retrofit2.http.POST
interface AuthenticationApi { interface AuthenticationApi {
@GET("authentication/guest_session/new")
suspend fun getNewGuestSession(): Response<GuestSessionResponse>
@HTTP(method = "DELETE", path = "authentication/session", hasBody = true)
suspend fun deleteSession(@Body body: SessionBody): Response<DeleteSessionResponse>
@GET("authentication/token/new")
suspend fun createRequestToken(): Response<CreateTokenResponse>
@POST("authentication/session/new")
suspend fun createSession(@Body body: TokenSessionBody): Response<CreateSessionResponse>
@POST("authentication/token/validate_with_login")
suspend fun validateTokenWithLogin(@Body body: TokenValidationBody): Response<CreateTokenResponse>
@POST("authentication/session/convert/4") @POST("authentication/session/convert/4")
suspend fun createSessionFromV4Token(@Body body: V4TokenBody): Response<CreateSessionResponse> suspend fun createSessionFromV4Token(@Body body: V4TokenBody): Response<CreateSessionResponse>
} }

View File

@@ -8,26 +8,6 @@ class AuthenticationService {
private val service by lazy { TmdbClient().createAuthenticationService() } private val service by lazy { TmdbClient().createAuthenticationService() }
suspend fun getNewGuestSession(): Response<GuestSessionResponse> {
return service.getNewGuestSession()
}
suspend fun deleteSession(body: SessionBody): Response<DeleteSessionResponse> {
return service.deleteSession(body)
}
suspend fun createRequestToken(): Response<CreateTokenResponse> {
return service.createRequestToken()
}
suspend fun createSession(body: TokenSessionBody): Response<CreateSessionResponse> {
return service.createSession(body)
}
suspend fun validateTokenWithLogin(body: TokenValidationBody): Response<CreateTokenResponse> {
return service.validateTokenWithLogin(body)
}
suspend fun createSessionFromV4Token(body: V4TokenBody): Response<CreateSessionResponse> { suspend fun createSessionFromV4Token(body: V4TokenBody): Response<CreateSessionResponse> {
return service.createSessionFromV4Token(body) return service.createSessionFromV4Token(body)
} }

View File

@@ -1,176 +0,0 @@
package com.owenlejeune.tvtime.ui.components
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.utils.SessionManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SignInDialog(
showDialog: MutableState<Boolean>,
onSuccess: (success: Boolean) -> Unit
) {
val context = LocalContext.current
var usernameState by rememberSaveable { mutableStateOf("") }
var usernameHasErrors by rememberSaveable { mutableStateOf(false) }
var usernameError = ""
var passwordState by rememberSaveable { mutableStateOf("") }
var passwordHasErrors by rememberSaveable { mutableStateOf(false) }
var passwordError = ""
fun validate(): Boolean {
usernameError = ""
passwordError = ""
if (TextUtils.isEmpty(usernameState)) {
usernameError = context.getString(R.string.username_not_empty_error)
}
if (TextUtils.isEmpty(passwordState)) {
passwordError = context.getString(R.string.password_empty_error)
}
usernameHasErrors = usernameError.isNotEmpty()
passwordHasErrors = passwordError.isNotEmpty()
return !usernameHasErrors && !passwordHasErrors
}
val focusManager = LocalFocusManager.current
AlertDialog(
title = { Text(text = stringResource(R.string.action_sign_in)) },
onDismissRequest = { showDialog.value = false },
confirmButton = { CancelButton(showDialog = showDialog) },
text = {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.sign_in_dialog_message)
)
ThemedOutlineTextField(
value = usernameState,
onValueChange = {
usernameHasErrors = false
usernameState = it
},
label = { Text(text = stringResource(R.string.username_label)) },
isError = usernameHasErrors,
errorMessage = usernameError,
singleLine = true
)
PasswordOutlineTextField(
value = passwordState,
onValueChange = {
passwordHasErrors = false
passwordState = it
},
label = { Text(text = stringResource(R.string.password_label)) },
isError = passwordHasErrors,
errorMessage = passwordError,
singleLine = true
)
SignInButton(username = usernameState, password = passwordState, validate = ::validate) { success ->
if (success) {
showDialog.value = false
} else {
Toast.makeText(context, "An error occurred, please try again", Toast.LENGTH_SHORT).show()
}
onSuccess(success)
}
CreateAccountLink()
}
}
)
}
@Composable
private fun CancelButton(showDialog: MutableState<Boolean>) {
TextButton(onClick = { showDialog.value = false }) {
Text(text = stringResource(R.string.action_cancel))
}
}
@Composable
private fun SignInButton(username: String, password: String, validate: () -> Boolean, onSuccess: (success: Boolean) -> Unit) {
var signInInProgress by remember { mutableStateOf(false) }
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if (!signInInProgress) {
if (validate()) {
signInInProgress = true
CoroutineScope(Dispatchers.IO).launch {
val success = SessionManager.signInWithLogin(username, password)
withContext(Dispatchers.Main) {
signInInProgress = false
onSuccess(success)
}
}
}
}
}
) {
if (signInInProgress) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = MaterialTheme.colorScheme.background,
strokeWidth = 2.dp
)
} else {
Text(text = stringResource(id = R.string.action_sign_in), color = MaterialTheme.colorScheme.background)
}
}
}
@Composable
private fun CreateAccountLink() {
val context = LocalContext.current
LinkableText(
text = stringResource(R.string.no_account_message),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.clickable(
onClick = {
val url = "https://www.themoviedb.org/signup"
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
context.startActivity(intent)
}
)
)
}

View File

@@ -1,12 +1,9 @@
package com.owenlejeune.tvtime.ui.screens.main package com.owenlejeune.tvtime.ui.screens.main
import android.content.Context import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -16,19 +13,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage import coil.compose.AsyncImage
@@ -43,7 +34,6 @@ import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.RoundedLetterImage import com.owenlejeune.tvtime.ui.components.RoundedLetterImage
import com.owenlejeune.tvtime.ui.components.SignInDialog
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.navigation.ListFetchFun import com.owenlejeune.tvtime.ui.navigation.ListFetchFun
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
@@ -114,13 +104,8 @@ fun AccountTab(
} }
Column { Column {
when (session.isAuthorized) { if (session.isAuthorized) {
true -> { AuthorizedSessionIcon()
AuthorizedSessionIcon()
}
false -> {
GuestSessionIcon()
}
} }
val pagerState = rememberPagerState() val pagerState = rememberPagerState()
@@ -342,7 +327,8 @@ private fun AccountDropdownMenu(
) { ) {
when(session?.isAuthorized) { when(session?.isAuthorized) {
true -> { AuthorizedSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) } true -> { AuthorizedSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
false -> { GuestSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) } // false -> { GuestSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
false -> {}
null -> { NoSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) } null -> { NoSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
} }
} }
@@ -360,60 +346,19 @@ private fun AuthorizedSessionMenuItems(
if (preferences.useV4Api) { if (preferences.useV4Api) {
signOutV4(lastSelectedOption) signOutV4(lastSelectedOption)
} else { } else {
signOut(lastSelectedOption) // signOut(lastSelectedOption)
} }
expanded.value = false expanded.value = false
} }
) )
} }
@Composable
private fun GuestSessionMenuItems(
expanded: MutableState<Boolean>,
lastSelectedOption: MutableState<String>
) {
val showSignInDialog = remember { mutableStateOf(false) }
if (showSignInDialog.value) {
SignInDialog(showDialog = showSignInDialog) { success ->
if (success) {
lastSelectedOption.value = GUEST_SIGN_IN
expanded.value = false
}
}
}
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in)) },
onClick = { showSignInDialog.value = true }
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_out)) },
onClick = {
signOut(lastSelectedOption)
expanded.value = false
}
)
}
@Composable @Composable
private fun NoSessionMenuItems( private fun NoSessionMenuItems(
expanded: MutableState<Boolean>, expanded: MutableState<Boolean>,
lastSelectedOption: MutableState<String>, lastSelectedOption: MutableState<String>,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
val showSignInDialog = remember { mutableStateOf(false) }
if (showSignInDialog.value) {
SignInDialog(showDialog = showSignInDialog) { success ->
if (success) {
lastSelectedOption.value = NO_SESSION_SIGN_IN
expanded.value = false
}
}
}
val context = LocalContext.current val context = LocalContext.current
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in)) }, text = { Text(text = stringResource(id = R.string.action_sign_in)) },
@@ -421,18 +366,10 @@ private fun NoSessionMenuItems(
if (preferences.useV4Api) { if (preferences.useV4Api) {
v4SignInPart1(context) v4SignInPart1(context)
} else { } else {
showSignInDialog.value = true // showSignInDialog.value = true
} }
} }
) )
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in_as_guest)) },
onClick = {
createGuestSession(lastSelectedOption)
expanded.value = false
}
)
} }
private fun v4SignInPart1(context: Context) { private fun v4SignInPart1(context: Context) {
@@ -452,12 +389,6 @@ private fun v4SignInPart2(lastSelectedOption: MutableState<String>) {
} }
} }
@Composable
private fun GuestSessionIcon() {
val guestName = stringResource(id = R.string.account_name_guest)
RoundedLetterImage(size = 60.dp, character = guestName[0])
}
@Composable @Composable
private fun AuthorizedSessionIcon() { private fun AuthorizedSessionIcon() {
val accountDetails = SessionManager.currentSession?.accountDetails val accountDetails = SessionManager.currentSession?.accountDetails
@@ -492,27 +423,6 @@ private fun AuthorizedSessionIcon() {
} }
} }
private fun createGuestSession(lastSelectedOption: MutableState<String>) {
CoroutineScope(Dispatchers.IO).launch {
val session = SessionManager.requestNewGuestSession()
if (session != null) {
withContext(Dispatchers.Main) {
lastSelectedOption.value = NO_SESSION_SIGN_IN_GUEST
}
}
}
}
private fun signOut(lastSelectedOption: MutableState<String>) {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.clearSession { isSuccessful ->
if (isSuccessful) {
lastSelectedOption.value = SIGN_OUT
}
}
}
}
private fun signOutV4(lastSelectedOption: MutableState<String>) { private fun signOutV4(lastSelectedOption: MutableState<String>) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
SessionManager.clearSessionV4 { isSuccessful -> SessionManager.clearSessionV4 { isSuccessful ->

View File

@@ -531,22 +531,6 @@ private fun CreateSessionDialog(showDialog: MutableState<Boolean>, onSessionRetu
}, },
text = { text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.requestNewGuestSession()?.let {
withContext(Dispatchers.Main) {
showDialog.value = false
onSessionReturned(true)
}
}
}
}
) {
Text(text = stringResource(R.string.action_continue_as_guest))
}
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = { onClick = {
@@ -1318,20 +1302,4 @@ private fun addToFavorite(
} }
} }
} }
}
@Composable
private fun ActionSnackBar(
message: String
) {
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
SnackbarHost(hostState = snackbarHostState)
LaunchedEffect(Unit) {
scope.launch {
snackbarHostState.showSnackbar(message)
}
}
} }

View File

@@ -482,11 +482,6 @@ private fun DevPreferences(
onClick = { onClick = {
preferences.guestSessionId = "" preferences.guestSessionId = ""
coroutineScope.launch { coroutineScope.launch {
SessionManager.clearSession {
Toast
.makeText(context, "Cleared session: $it", Toast.LENGTH_SHORT)
.show()
}
SessionManager.clearSessionV4 { SessionManager.clearSessionV4 {
Toast Toast
.makeText( .makeText(

View File

@@ -1,19 +1,13 @@
package com.owenlejeune.tvtime.utils package com.owenlejeune.tvtime.utils
import android.accounts.Account
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService
import com.owenlejeune.tvtime.api.tmdb.api.v3.GuestSessionService
import com.owenlejeune.tvtime.api.tmdb.TmdbClient import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountApi import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Api
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody
@@ -48,22 +42,6 @@ object SessionManager: KoinComponent {
@SerializedName("account_id") val accountId: String @SerializedName("account_id") val accountId: String
) )
suspend fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) {
currentSession?.let { session ->
CoroutineScope(Dispatchers.IO).launch {
val deleteResponse = authenticationService.deleteSession(SessionBody(session.sessionId))
withContext(Dispatchers.Main) {
if (deleteResponse.isSuccessful) {
_currentSession = null
preferences.guestSessionId = ""
preferences.authorizedSessionValues = null
}
onResponse(deleteResponse.isSuccessful)
}
}
}
}
fun clearSessionV4(onResponse: (isSuccessful: Boolean) -> Unit) { fun clearSessionV4(onResponse: (isSuccessful: Boolean) -> Unit) {
currentSession?.let { session -> currentSession?.let { session ->
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
@@ -81,68 +59,17 @@ object SessionManager: KoinComponent {
} }
suspend fun initialize() { suspend fun initialize() {
if (preferences.guestSessionId.isNotEmpty()) { preferences.authorizedSessionValues?.let { values ->
val session = GuestSession() val session = AuthorizedSession(
session.initialize() sessionId = values.sessionId,
accessToken = values.accessToken,
accountId = values.accountId
)
_currentSession = session _currentSession = session
} else if (preferences.authorizedSessionId.isNotEmpty()) {
val session = AuthorizedSession()
session.initialize() session.initialize()
_currentSession = session
} else {
preferences.authorizedSessionValues?.let { values ->
val session = AuthorizedSession(
sessionId = values.sessionId,
accessToken = values.accessToken,
accountId = values.accountId
)
_currentSession = session
session.initialize()
}
} }
} }
suspend fun requestNewGuestSession(): Session? {
val response = authenticationService.getNewGuestSession()
if (response.isSuccessful) {
preferences.guestSessionId = response.body()?.guestSessionId ?: ""
_currentSession = GuestSession()
}
return _currentSession
}
suspend fun signInWithLogin(username: String, password: String): Boolean {
val service = AuthenticationService()
val createTokenResponse = service.createRequestToken()
if (createTokenResponse.isSuccessful) {
createTokenResponse.body()?.let { ctr ->
val body = TokenValidationBody(username, password, ctr.requestToken)
val loginResponse = service.validateTokenWithLogin(body)
if (loginResponse.isSuccessful) {
loginResponse.body()?.let { lr ->
if (lr.success) {
val sessionBody = TokenSessionBody(lr.requestToken)
val sessionResponse = service.createSession(sessionBody)
if (sessionResponse.isSuccessful) {
sessionResponse.body()?.let { sr ->
if (sr.isSuccess) {
preferences.authorizedSessionId = sr.sessionId
preferences.guestSessionId = ""
preferences.authorizedSessionValues = null
_currentSession = AuthorizedSession()
_currentSession?.initialize()
return true
}
}
}
}
}
}
}
}
return false
}
suspend fun signInWithV4Part1(context: Context) { suspend fun signInWithV4Part1(context: Context) {
isV4SignInInProgress = true isV4SignInInProgress = true
@@ -409,42 +336,4 @@ object SessionManager: KoinComponent {
} }
} }
private class GuestSession: Session(preferences.guestSessionId, false) {
private val service by lazy { GuestSessionService() }
override suspend fun initialize() {
refresh()
}
override suspend fun refresh(changed: Array<Changed>) {
if (changed.contains(Changed.RatedMovies)) {
service.getRatedMovies(sessionId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
_ratedMovies = body()?.results ?: _ratedMovies
}
}
}
}
if (changed.contains(Changed.RatedTv)) {
service.getRatedTvShows(sessionId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
_ratedTvShows = body()?.results ?: _ratedTvShows
}
}
}
}
if (changed.contains(Changed.RatedEpisodes)) {
service.getRatedTvEpisodes(sessionId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
_ratedTvEpisodes = body()?.results ?: _ratedTvEpisodes
}
}
}
}
}
}
} }