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 {
@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")
suspend fun createSessionFromV4Token(@Body body: V4TokenBody): Response<CreateSessionResponse>
}

View File

@@ -8,26 +8,6 @@ class AuthenticationService {
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> {
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
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material3.*
@@ -16,19 +13,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
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.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage
@@ -43,7 +34,6 @@ 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.components.SignInDialog
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.navigation.ListFetchFun
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
@@ -114,13 +104,8 @@ fun AccountTab(
}
Column {
when (session.isAuthorized) {
true -> {
AuthorizedSessionIcon()
}
false -> {
GuestSessionIcon()
}
if (session.isAuthorized) {
AuthorizedSessionIcon()
}
val pagerState = rememberPagerState()
@@ -342,7 +327,8 @@ private fun AccountDropdownMenu(
) {
when(session?.isAuthorized) {
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) }
}
}
@@ -360,60 +346,19 @@ private fun AuthorizedSessionMenuItems(
if (preferences.useV4Api) {
signOutV4(lastSelectedOption)
} else {
signOut(lastSelectedOption)
// signOut(lastSelectedOption)
}
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
private fun NoSessionMenuItems(
expanded: MutableState<Boolean>,
lastSelectedOption: MutableState<String>,
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
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in)) },
@@ -421,18 +366,10 @@ private fun NoSessionMenuItems(
if (preferences.useV4Api) {
v4SignInPart1(context)
} 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) {
@@ -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
private fun AuthorizedSessionIcon() {
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>) {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.clearSessionV4 { isSuccessful ->

View File

@@ -531,22 +531,6 @@ private fun CreateSessionDialog(showDialog: MutableState<Boolean>, onSessionRetu
},
text = {
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(
modifier = Modifier.fillMaxWidth(),
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 = {
preferences.guestSessionId = ""
coroutineScope.launch {
SessionManager.clearSession {
Toast
.makeText(context, "Cleared session: $it", Toast.LENGTH_SHORT)
.show()
}
SessionManager.clearSessionV4 {
Toast
.makeText(

View File

@@ -1,19 +1,13 @@
package com.owenlejeune.tvtime.utils
import android.accounts.Account
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import com.google.gson.annotations.SerializedName
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.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.v4.AccountV4Api
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.model.AuthAccessBody
@@ -48,22 +42,6 @@ object SessionManager: KoinComponent {
@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) {
currentSession?.let { session ->
CoroutineScope(Dispatchers.IO).launch {
@@ -81,68 +59,17 @@ object SessionManager: KoinComponent {
}
suspend fun initialize() {
if (preferences.guestSessionId.isNotEmpty()) {
val session = GuestSession()
session.initialize()
preferences.authorizedSessionValues?.let { values ->
val session = AuthorizedSession(
sessionId = values.sessionId,
accessToken = values.accessToken,
accountId = values.accountId
)
_currentSession = session
} else if (preferences.authorizedSessionId.isNotEmpty()) {
val session = AuthorizedSession()
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) {
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
}
}
}
}
}
}
}