mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-22 19:50:54 -05:00
add sign in menu to account tab
This commit is contained in:
@@ -59,6 +59,7 @@ dependencies {
|
|||||||
implementation "androidx.compose.ui:ui:${Versions.compose}"
|
implementation "androidx.compose.ui:ui:${Versions.compose}"
|
||||||
implementation "androidx.compose.material3:material3:${Versions.compose_material3}"
|
implementation "androidx.compose.material3:material3:${Versions.compose_material3}"
|
||||||
implementation "androidx.compose.material:material:${Versions.compose}"
|
implementation "androidx.compose.material:material:${Versions.compose}"
|
||||||
|
implementation "androidx.compose.material:material-icons-extended:${Versions.compose}"
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview:${Versions.compose}"
|
implementation "androidx.compose.ui:ui-tooling-preview:${Versions.compose}"
|
||||||
implementation "androidx.activity:activity-compose:${Versions.activity_compose}"
|
implementation "androidx.activity:activity-compose:${Versions.activity_compose}"
|
||||||
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
|
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DeleteSessionBody
|
import com.owenlejeune.tvtime.api.tmdb.model.*
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DeleteSessionResponse
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.GuestSessionResponse
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.DELETE
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.HTTP
|
import retrofit2.http.HTTP
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
interface AuthenticationApi {
|
interface AuthenticationApi {
|
||||||
|
|
||||||
@GET("authentication/guest_session/new")
|
@GET("authentication/guest_session/new")
|
||||||
suspend fun getNewGuestSession(): Response<GuestSessionResponse>
|
suspend fun getNewGuestSession(): Response<GuestSessionResponse>
|
||||||
|
|
||||||
// @DELETE("authentication/session")
|
|
||||||
@HTTP(method = "DELETE", path = "authentication/session", hasBody = true)
|
@HTTP(method = "DELETE", path = "authentication/session", hasBody = true)
|
||||||
suspend fun deleteSession(@Body body: DeleteSessionBody): Response<DeleteSessionResponse>
|
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: SessionBody): Response<CreateSessionResponse>
|
||||||
|
|
||||||
|
@POST("authentication/token/validate_with_login")
|
||||||
|
suspend fun validateTokenWithLogin(@Body body: TokenValidationBody): Response<CreateTokenResponse>
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DeleteSessionBody
|
import com.owenlejeune.tvtime.api.tmdb.model.SessionBody
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DeleteSessionResponse
|
import com.owenlejeune.tvtime.api.tmdb.model.DeleteSessionResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.GuestSessionResponse
|
import com.owenlejeune.tvtime.api.tmdb.model.GuestSessionResponse
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
@@ -13,7 +13,7 @@ class AuthenticationService {
|
|||||||
return service.getNewGuestSession()
|
return service.getNewGuestSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteSession(body: DeleteSessionBody): Response<DeleteSessionResponse> {
|
suspend fun deleteSession(body: SessionBody): Response<DeleteSessionResponse> {
|
||||||
return service.deleteSession(body)
|
return service.deleteSession(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
|||||||
|
|
||||||
override suspend fun postRating(id: Int, rating: RatingBody): Response<RatingResponse> {
|
override suspend fun postRating(id: Int, rating: RatingBody): Response<RatingResponse> {
|
||||||
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
||||||
return if (session.isGuest) {
|
return if (!session.isAuthorized) {
|
||||||
movieService.postMovieRatingAsGuest(id, session.sessionId, rating)
|
movieService.postMovieRatingAsGuest(id, session.sessionId, rating)
|
||||||
} else {
|
} else {
|
||||||
movieService.postMovieRatingAsUser(id, session.sessionId, rating)
|
movieService.postMovieRatingAsUser(id, session.sessionId, rating)
|
||||||
@@ -64,7 +64,7 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
|||||||
|
|
||||||
override suspend fun deleteRating(id: Int): Response<RatingResponse> {
|
override suspend fun deleteRating(id: Int): Response<RatingResponse> {
|
||||||
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
||||||
return if (session.isGuest) {
|
return if (!session.isAuthorized) {
|
||||||
movieService.deleteMovieReviewAsGuest(id, session.sessionId)
|
movieService.deleteMovieReviewAsGuest(id, session.sessionId)
|
||||||
} else {
|
} else {
|
||||||
movieService.deleteMovieReviewAsUser(id, session.sessionId)
|
movieService.deleteMovieReviewAsUser(id, session.sessionId)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
|
|
||||||
override suspend fun postRating(id: Int, rating: RatingBody): Response<RatingResponse> {
|
override suspend fun postRating(id: Int, rating: RatingBody): Response<RatingResponse> {
|
||||||
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
||||||
return if (session.isGuest) {
|
return if (!session.isAuthorized) {
|
||||||
service.postTvRatingAsGuest(id, session.sessionId, rating)
|
service.postTvRatingAsGuest(id, session.sessionId, rating)
|
||||||
} else {
|
} else {
|
||||||
service.postTvRatingAsUser(id, session.sessionId, rating)
|
service.postTvRatingAsUser(id, session.sessionId, rating)
|
||||||
@@ -64,7 +64,7 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
|
|
||||||
override suspend fun deleteRating(id: Int): Response<RatingResponse> {
|
override suspend fun deleteRating(id: Int): Response<RatingResponse> {
|
||||||
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
val session = SessionManager.currentSession ?: throw Exception("Session must not be null")
|
||||||
return if (session.isGuest) {
|
return if (!session.isAuthorized) {
|
||||||
service.deleteTvReviewAsGuest(id, session.sessionId)
|
service.deleteTvReviewAsGuest(id, session.sessionId)
|
||||||
} else {
|
} else {
|
||||||
service.deleteTvReviewAsUser(id, session.sessionId)
|
service.deleteTvReviewAsUser(id, session.sessionId)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class CreateSessionResponse(
|
||||||
|
@SerializedName("success") val isSuccess: Boolean,
|
||||||
|
@SerializedName("session_idd") val sessionId: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class CreateTokenResponse(
|
||||||
|
@SerializedName("success") val success: Boolean,
|
||||||
|
@SerializedName("request_token") val requestToken: String,
|
||||||
|
@SerializedName("expires_at") val expiry: String
|
||||||
|
)
|
||||||
@@ -2,6 +2,6 @@ package com.owenlejeune.tvtime.api.tmdb.model
|
|||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
class DeleteSessionBody(
|
class SessionBody(
|
||||||
@SerializedName("session_id") val sessionsId: String
|
@SerializedName("session_id") val sessionsId: String
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class TokenValidationBody(
|
||||||
|
@SerializedName("username") val username: String,
|
||||||
|
@SerializedName("password") val password: String,
|
||||||
|
@SerializedName("request_token") val requestToken: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.owenlejeune.tvtime.extensions
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Patterns
|
||||||
|
|
||||||
|
fun String.isEmailValid(): Boolean {
|
||||||
|
return !TextUtils.isEmpty(this) && Patterns.EMAIL_ADDRESS.matcher(this).matches()
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ class AppPreferences(context: Context) {
|
|||||||
private val PERSISTENT_SEARCH = "persistent_search"
|
private val PERSISTENT_SEARCH = "persistent_search"
|
||||||
private val HIDE_TITLE = "hide_title"
|
private val HIDE_TITLE = "hide_title"
|
||||||
private val GUEST_SESSION = "guest_session_id"
|
private val GUEST_SESSION = "guest_session_id"
|
||||||
|
private val AUTHORIZED_SESSION = "authorized_session_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
|
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
|
||||||
@@ -27,6 +28,10 @@ class AppPreferences(context: Context) {
|
|||||||
var guestSessionId: String
|
var guestSessionId: String
|
||||||
get() = preferences.getString(GUEST_SESSION, "") ?: ""
|
get() = preferences.getString(GUEST_SESSION, "") ?: ""
|
||||||
set(value) { preferences.put(GUEST_SESSION, value) }
|
set(value) { preferences.put(GUEST_SESSION, value) }
|
||||||
|
|
||||||
|
var authorizedSessionId: String
|
||||||
|
get() = preferences.getString(AUTHORIZED_SESSION, "") ?: ""
|
||||||
|
set(value) { preferences.put(AUTHORIZED_SESSION, value) }
|
||||||
// val usePreferences: MutableState<Boolean>
|
// val usePreferences: MutableState<Boolean>
|
||||||
// var usePreferences: Boolean
|
// var usePreferences: Boolean
|
||||||
// get() = preferences.getBoolean(USE_PREFERENCES, false)
|
// get() = preferences.getBoolean(USE_PREFERENCES, false)
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.owenlejeune.tvtime.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.AlertDialog
|
||||||
|
import androidx.compose.material.DropdownMenu
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TopAppBarDropdownMenu(
|
||||||
|
icon: @Composable () -> Unit = {},
|
||||||
|
content: @Composable ColumnScope.(expanded: MutableState<Boolean>) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val expanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.wrapContentSize(Alignment.TopEnd)
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
expanded.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
modifier = Modifier.background(color = MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
expanded = expanded.value,
|
||||||
|
onDismissRequest = { expanded.value = false }
|
||||||
|
) {
|
||||||
|
content(this, expanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TopAppBarDialogMenu(
|
||||||
|
icon: @Composable () -> Unit = {},
|
||||||
|
content: @Composable (showing: MutableState<Boolean>) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val expanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.wrapContentSize(Alignment.TopEnd)
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
expanded.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expanded.value) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { expanded.value = false },
|
||||||
|
content = { content(expanded) }
|
||||||
|
)
|
||||||
|
// AlertDialog(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .fillMaxWidth()
|
||||||
|
// .wrapContentHeight(),
|
||||||
|
// backgroundColor = MaterialTheme.colorScheme.background,
|
||||||
|
// onDismissRequest = { expanded.value = false },
|
||||||
|
// text = { content(expanded) },
|
||||||
|
// buttons = {}
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
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.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.owenlejeune.tvtime.R
|
||||||
|
import com.owenlejeune.tvtime.extensions.isEmailValid
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignInDialog(
|
||||||
|
showDialog: MutableState<Boolean>,
|
||||||
|
onSuccess: (success: Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
var emailState by rememberSaveable { mutableStateOf("") }
|
||||||
|
var emailHasErrors by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var emailError = ""
|
||||||
|
|
||||||
|
var passwordState by rememberSaveable { mutableStateOf("") }
|
||||||
|
var passwordHasErrors by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var passwordError = ""
|
||||||
|
|
||||||
|
fun validate(): Boolean {
|
||||||
|
emailError = ""
|
||||||
|
passwordError = ""
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(emailState)) {
|
||||||
|
emailError = context.getString(R.string.email_not_empty_error)
|
||||||
|
} else if (!emailState.isEmailValid()) {
|
||||||
|
emailError = context.getString(R.string.email_invalid_error)
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(passwordState)) {
|
||||||
|
passwordError = context.getString(R.string.password_empty_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
emailHasErrors = emailError.isNotEmpty()
|
||||||
|
passwordHasErrors = passwordError.isNotEmpty()
|
||||||
|
|
||||||
|
return !emailHasErrors && !passwordHasErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = emailState,
|
||||||
|
onValueChange = {
|
||||||
|
emailHasErrors = false
|
||||||
|
emailState = it
|
||||||
|
},
|
||||||
|
label = { Text(text = stringResource(R.string.email_label)) },
|
||||||
|
isError = emailHasErrors,
|
||||||
|
errorMessage = emailError
|
||||||
|
)
|
||||||
|
PasswordOutlineTextField(
|
||||||
|
value = passwordState,
|
||||||
|
onValueChange = {
|
||||||
|
passwordHasErrors = false
|
||||||
|
passwordState = it
|
||||||
|
},
|
||||||
|
label = { Text(text = stringResource(R.string.password_label)) },
|
||||||
|
isError = passwordHasErrors,
|
||||||
|
errorMessage = passwordError
|
||||||
|
)
|
||||||
|
SignInButton(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(validate: () -> Boolean, onSuccess: (success: Boolean) -> Unit) {
|
||||||
|
var signInInProgress by remember { mutableStateOf(false) }
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
if (!signInInProgress) {
|
||||||
|
if (validate()) {
|
||||||
|
signInInProgress = true
|
||||||
|
// signIn(context, emailState, passwordState) { success ->
|
||||||
|
// signInInProgress = false
|
||||||
|
// if (success) {
|
||||||
|
// showDialog.value = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
onSuccess(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import android.widget.Toast
|
|||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||||
@@ -16,10 +17,17 @@ import androidx.compose.foundation.text.BasicTextField
|
|||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.TextFieldColors
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Error
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -30,21 +38,23 @@ import androidx.compose.ui.focus.focusRequester
|
|||||||
import androidx.compose.ui.focus.onFocusEvent
|
import androidx.compose.ui.focus.onFocusEvent
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.graphics.painter.BrushPainter
|
import androidx.compose.ui.graphics.painter.BrushPainter
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.TextLayoutResult
|
import androidx.compose.ui.text.TextLayoutResult
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -653,6 +663,37 @@ fun AvatarImage(
|
|||||||
contentDescription = ""
|
contentDescription = ""
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
val text = if (author.name.isNotEmpty()) author.name[0] else author.username[0]
|
||||||
|
RoundedLetterImage(
|
||||||
|
size = size,
|
||||||
|
character = text
|
||||||
|
)
|
||||||
|
// Box(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .clip(CircleShape)
|
||||||
|
// .size(size)
|
||||||
|
// .background(color = MaterialTheme.colorScheme.tertiary)
|
||||||
|
// ) {
|
||||||
|
// Text(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .fillMaxSize()
|
||||||
|
// .padding(top = size / 5),
|
||||||
|
// text = if (author.name.isNotEmpty()) author.name[0].uppercase() else author.username[0].toString(),
|
||||||
|
// color = MaterialTheme.colorScheme.onTertiary,
|
||||||
|
// textAlign = TextAlign.Center,
|
||||||
|
// style = MaterialTheme.typography.titleLarge
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RoundedLetterImage(
|
||||||
|
size: Dp,
|
||||||
|
character: Char,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
topPadding: Dp = size / 5
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
@@ -662,12 +703,169 @@ fun AvatarImage(
|
|||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = size / 5),
|
.padding(top = topPadding),
|
||||||
text = if (author.name.isNotEmpty()) author.name[0].uppercase() else author.username[0].toString(),
|
text = character.uppercase(),
|
||||||
color = MaterialTheme.colorScheme.onTertiary,
|
color = MaterialTheme.colorScheme.onTertiary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.titleLarge
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThemedOutlineTextField(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
textStyle: TextStyle = androidx.compose.material.LocalTextStyle.current,
|
||||||
|
label: @Composable (() -> Unit)? = null,
|
||||||
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
isError: Boolean = false,
|
||||||
|
errorMessage: String = "",
|
||||||
|
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
|
singleLine: Boolean = false,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
shape: Shape = androidx.compose.material.MaterialTheme.shapes.small
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
enabled = enabled,
|
||||||
|
readOnly = readOnly,
|
||||||
|
textStyle = textStyle,
|
||||||
|
label = label,
|
||||||
|
placeholder = placeholder,
|
||||||
|
leadingIcon = leadingIcon,
|
||||||
|
trailingIcon = {
|
||||||
|
if (isError) {
|
||||||
|
Icon(Icons.Filled.Error, "error", tint = MaterialTheme.colorScheme.error)
|
||||||
|
} else {
|
||||||
|
trailingIcon?.invoke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isError = isError,
|
||||||
|
visualTransformation = visualTransformation,
|
||||||
|
keyboardOptions = keyboardOptions,
|
||||||
|
keyboardActions = keyboardActions,
|
||||||
|
singleLine = singleLine,
|
||||||
|
maxLines = maxLines,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
shape = shape,
|
||||||
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
unfocusedBorderColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
||||||
|
focusedLabelColor = MaterialTheme.colorScheme.primary,
|
||||||
|
cursorColor = MaterialTheme.colorScheme.primary,
|
||||||
|
textColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
errorBorderColor = MaterialTheme.colorScheme.error,
|
||||||
|
errorCursorColor = MaterialTheme.colorScheme.error,
|
||||||
|
errorLabelColor = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (isError) {
|
||||||
|
Text(
|
||||||
|
text = errorMessage,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.padding(start = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PasswordOutlineTextField(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
textStyle: TextStyle = androidx.compose.material.LocalTextStyle.current,
|
||||||
|
label: @Composable (() -> Unit)? = null,
|
||||||
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
isError: Boolean = false,
|
||||||
|
errorMessage: String = "",
|
||||||
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
|
singleLine: Boolean = false,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
shape: Shape = androidx.compose.material.MaterialTheme.shapes.small
|
||||||
|
) {
|
||||||
|
var passwordVisible by rememberSaveable { mutableStateOf(false) }
|
||||||
|
ThemedOutlineTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
readOnly = readOnly,
|
||||||
|
textStyle = textStyle,
|
||||||
|
label = label,
|
||||||
|
placeholder = placeholder,
|
||||||
|
leadingIcon = leadingIcon,
|
||||||
|
isError = isError,
|
||||||
|
errorMessage = errorMessage,
|
||||||
|
keyboardActions = keyboardActions,
|
||||||
|
singleLine = singleLine,
|
||||||
|
maxLines = maxLines,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
shape = shape,
|
||||||
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
trailingIcon = {
|
||||||
|
val image = if (passwordVisible) {
|
||||||
|
Icons.Filled.Visibility
|
||||||
|
} else {
|
||||||
|
Icons.Filled.VisibilityOff
|
||||||
|
}
|
||||||
|
val description = if (passwordVisible) "Hide password" else "Show password"
|
||||||
|
IconButton(onClick = { passwordVisible = !passwordVisible } ) {
|
||||||
|
Icon(imageVector = image, contentDescription = description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LinkableText(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
fontSize: TextUnit = TextUnit.Unspecified,
|
||||||
|
fontStyle: FontStyle? = null,
|
||||||
|
fontWeight: FontWeight? = null,
|
||||||
|
fontFamily: FontFamily? = null,
|
||||||
|
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||||
|
textAlign: TextAlign? = null,
|
||||||
|
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||||
|
overflow: TextOverflow = TextOverflow.Clip,
|
||||||
|
softWrap: Boolean = true,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
|
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||||
|
style: TextStyle = LocalTextStyle.current
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = modifier,
|
||||||
|
color = Color(0xFF64B5F6),
|
||||||
|
fontSize = fontSize,
|
||||||
|
fontStyle = fontStyle,
|
||||||
|
fontWeight = fontWeight,
|
||||||
|
fontFamily = fontFamily,
|
||||||
|
letterSpacing = letterSpacing,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
textAlign = textAlign,
|
||||||
|
lineHeight = lineHeight,
|
||||||
|
overflow = overflow,
|
||||||
|
softWrap = softWrap,
|
||||||
|
maxLines = maxLines,
|
||||||
|
onTextLayout = onTextLayout,
|
||||||
|
style = style
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ sealed class AccountTabNavItem(stringRes: Int, route: String, val mediaType: Med
|
|||||||
override val name = resourceUtils.getString(stringRes)
|
override val name = resourceUtils.getString(stringRes)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val GuestItems = listOf(RatedMovies, RatedTvShows)//, RatedTvEpisodes)
|
val GuestItems = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
object RatedMovies: AccountTabNavItem(R.string.nav_rated_movies_title, "rated_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.ratedMovies ?: emptyList() } )
|
object RatedMovies: AccountTabNavItem(R.string.nav_rated_movies_title, "rated_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.ratedMovies ?: emptyList() } )
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.ui.navigation
|
package com.owenlejeune.tvtime.ui.navigation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -20,8 +21,12 @@ object NavConstants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar: MutableState<Boolean> = mutableStateOf(false)) {
|
fun MainNavigationRoutes(
|
||||||
NavHost(navController = navController, startDestination = MainNavItem.MainView.route) {
|
navController: NavHostController,
|
||||||
|
displayUnderStatusBar: MutableState<Boolean> = mutableStateOf(false),
|
||||||
|
startDestination: String = MainNavItem.MainView.route
|
||||||
|
) {
|
||||||
|
NavHost(navController = navController, startDestination = startDestination) {
|
||||||
composable(MainNavItem.MainView.route) {
|
composable(MainNavItem.MainView.route) {
|
||||||
displayUnderStatusBar.value = false
|
displayUnderStatusBar.value = false
|
||||||
MainAppView(appNavController = navController)
|
MainAppView(appNavController = navController)
|
||||||
@@ -56,25 +61,31 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar
|
|||||||
fun BottomNavigationRoutes(
|
fun BottomNavigationRoutes(
|
||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
appBarTitle: MutableState<String>
|
appBarTitle: MutableState<String>,
|
||||||
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
|
||||||
) {
|
) {
|
||||||
NavHost(navController = navController, startDestination = BottomNavItem.Movies.route) {
|
NavHost(navController = navController, startDestination = BottomNavItem.Movies.route) {
|
||||||
composable(BottomNavItem.Movies.route) {
|
composable(BottomNavItem.Movies.route) {
|
||||||
|
appBarActions.value = {}
|
||||||
MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE)
|
MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.TV.route) {
|
composable(BottomNavItem.TV.route) {
|
||||||
|
appBarActions.value = {}
|
||||||
MediaTab(appNavController = appNavController, mediaType = MediaViewType.TV)
|
MediaTab(appNavController = appNavController, mediaType = MediaViewType.TV)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Account.route) {
|
composable(BottomNavItem.Account.route) {
|
||||||
AccountTab(appBarTitle = appBarTitle, appNavController = appNavController)
|
AccountTab(appBarTitle = appBarTitle, appNavController = appNavController, appBarActions = appBarActions)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.People.route) {
|
composable(BottomNavItem.People.route) {
|
||||||
|
appBarActions.value = {}
|
||||||
PeopleTab(appBarTitle, appNavController = appNavController)
|
PeopleTab(appBarTitle, appNavController = appNavController)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Favourites.route) {
|
composable(BottomNavItem.Favourites.route) {
|
||||||
|
appBarActions.value = {}
|
||||||
FavouritesTab()
|
FavouritesTab()
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Settings.route) {
|
composable(BottomNavItem.Settings.route) {
|
||||||
|
appBarActions.value = {}
|
||||||
SettingsTab()
|
SettingsTab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ package com.owenlejeune.tvtime.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.animation.rememberSplineBasedDecay
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
@@ -40,7 +38,7 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
|
|||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
val appBarTitle = remember { mutableStateOf(BottomNavItem.Items[0].name) }
|
val appBarTitle = rememberSaveable { mutableStateOf(BottomNavItem.getByRoute(currentRoute)?.name ?: BottomNavItem.Items[0].name) }
|
||||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
val scrollBehavior = remember(decayAnimationSpec) {
|
val scrollBehavior = remember(decayAnimationSpec) {
|
||||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec)
|
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec)
|
||||||
@@ -50,6 +48,8 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
|
|||||||
val focusSearchBar = remember { mutableStateOf(false) }
|
val focusSearchBar = remember { mutableStateOf(false) }
|
||||||
val searchableScreens = listOf(BottomNavItem.Movies.route, BottomNavItem.TV.route, BottomNavItem.People.route)
|
val searchableScreens = listOf(BottomNavItem.Movies.route, BottomNavItem.TV.route, BottomNavItem.People.route)
|
||||||
|
|
||||||
|
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>( {} ) }
|
||||||
|
|
||||||
// todo - scroll state not remember when returing from detail screen
|
// todo - scroll state not remember when returing from detail screen
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -72,7 +72,8 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
|
|||||||
} else {
|
} else {
|
||||||
TopBar(
|
TopBar(
|
||||||
title = appBarTitle,
|
title = appBarTitle,
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior,
|
||||||
|
appBarActions = appBarActions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -86,7 +87,7 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
|
|||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(modifier = Modifier.padding(innerPadding)) {
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
BottomNavigationRoutes(appNavController = appNavController, navController = navController, appBarTitle = appBarTitle)
|
BottomNavigationRoutes(appNavController = appNavController, navController = navController, appBarTitle = appBarTitle, appBarActions = appBarActions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,8 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
title: MutableState<String>,
|
title: MutableState<String>,
|
||||||
scrollBehavior: TopAppBarScrollBehavior
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
|
||||||
) {
|
) {
|
||||||
LargeTopAppBar(
|
LargeTopAppBar(
|
||||||
title = { Text(text = title.value) },
|
title = { Text(text = title.value) },
|
||||||
@@ -103,7 +105,8 @@ private fun TopBar(
|
|||||||
.largeTopAppBarColors(
|
.largeTopAppBarColors(
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||||
titleContentColor = MaterialTheme.colorScheme.primary
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
),
|
||||||
|
actions = appBarActions.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ private fun ActionsView(
|
|||||||
service = service
|
service = service
|
||||||
)
|
)
|
||||||
|
|
||||||
if (session?.isGuest == false) {
|
if (session?.isAuthorized == true) {
|
||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
text = stringResource(R.string.add_to_list_action_label),
|
text = stringResource(R.string.add_to_list_action_label),
|
||||||
@@ -641,7 +641,7 @@ private fun ReviewsCard(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
if (SessionManager.currentSession?.isGuest == false) {
|
if (SessionManager.currentSession?.isAuthorized == true) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens.tabs.bottom
|
package com.owenlejeune.tvtime.ui.screens.tabs.bottom
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
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.material.Text
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.AccountCircle
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
@@ -17,9 +27,13 @@ import com.google.accompanist.pager.ExperimentalPagerApi
|
|||||||
import com.google.accompanist.pager.HorizontalPager
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
import com.google.accompanist.pager.PagerState
|
import com.google.accompanist.pager.PagerState
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.RatedMovie
|
import com.owenlejeune.tvtime.api.tmdb.model.RatedMovie
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.RatedTopLevelMedia
|
import com.owenlejeune.tvtime.api.tmdb.model.RatedTopLevelMedia
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.RatedTv
|
import com.owenlejeune.tvtime.api.tmdb.model.RatedTv
|
||||||
|
import com.owenlejeune.tvtime.ui.components.RoundedLetterImage
|
||||||
|
import com.owenlejeune.tvtime.ui.components.SignInDialog
|
||||||
|
import com.owenlejeune.tvtime.ui.components.TopAppBarDropdownMenu
|
||||||
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
|
||||||
@@ -27,18 +41,39 @@ import com.owenlejeune.tvtime.ui.screens.MediaViewType
|
|||||||
import com.owenlejeune.tvtime.ui.screens.tabs.top.Tabs
|
import com.owenlejeune.tvtime.ui.screens.tabs.top.Tabs
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
private const val GUEST_SIGN_IN = "guest_sign_in"
|
||||||
|
private const val SIGN_OUT = "sign_out"
|
||||||
|
private const val ACCOUNT_SIGN_OUT = "account_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)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountTab(appNavController: NavHostController, appBarTitle: MutableState<String>) {
|
fun AccountTab(
|
||||||
if (SessionManager.currentSession?.isGuest == true) {
|
appNavController: NavHostController,
|
||||||
appBarTitle.value = "Hello, Guest"
|
appBarTitle: MutableState<String>,
|
||||||
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
|
||||||
|
) {
|
||||||
|
if (SessionManager.currentSession?.isAuthorized == false) {
|
||||||
|
appBarTitle.value = stringResource(id = R.string.account_header_title_formatted).replace("%1\$s", stringResource(id = R.string.account_name_guest))
|
||||||
} else {
|
} else {
|
||||||
appBarTitle.value = "Not logged in"
|
appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lastSelectedOption = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
appBarActions.value = {
|
||||||
|
AccountDropdownMenu(session = SessionManager.currentSession, lastSelectedOption = lastSelectedOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSelectedOption.value.isNotBlank() || lastSelectedOption.value.isBlank()) {
|
||||||
SessionManager.currentSession?.let { session ->
|
SessionManager.currentSession?.let { session ->
|
||||||
val tabs = if (session.isGuest) {
|
val tabs = if (session.isAuthorized) {
|
||||||
AccountTabNavItem.GuestItems
|
AccountTabNavItem.GuestItems
|
||||||
} else {
|
} else {
|
||||||
AccountTabNavItem.GuestItems
|
AccountTabNavItem.GuestItems
|
||||||
@@ -55,6 +90,7 @@ fun AccountTab(appNavController: NavHostController, appBarTitle: MutableState<St
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountTabContent(
|
fun AccountTabContent(
|
||||||
@@ -64,8 +100,22 @@ fun AccountTabContent(
|
|||||||
) {
|
) {
|
||||||
val contentItems = listFetchFun()
|
val contentItems = listFetchFun()
|
||||||
|
|
||||||
// if (contentItems.isNotEmpty() && contentItems[0] is RatedTopLevelMedia) {
|
LazyColumn(modifier = Modifier
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(12.dp)) {
|
.fillMaxSize()
|
||||||
|
.padding(12.dp)) {
|
||||||
|
if (contentItems.isEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 20.dp, vertical = 15.dp),
|
||||||
|
text = stringResource(R.string.no_rated_content_message),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
items(contentItems.size) { i ->
|
items(contentItems.size) { i ->
|
||||||
val ratedItem = contentItems[i] as RatedTopLevelMedia
|
val ratedItem = contentItems[i] as RatedTopLevelMedia
|
||||||
|
|
||||||
@@ -110,15 +160,162 @@ fun AccountTabContent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Rating: ${(ratedItem.rating * 10).toInt()}%",
|
text = stringResource(id = R.string.rating_test, (ratedItem.rating * 10).toInt()),
|
||||||
color = MaterialTheme.colorScheme.onBackground
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AccountDropdownMenu(
|
||||||
|
session: SessionManager.Session?,
|
||||||
|
lastSelectedOption: MutableState<String>
|
||||||
|
) {
|
||||||
|
TopAppBarDropdownMenu(
|
||||||
|
icon = {
|
||||||
|
when(session?.isAuthorized) {
|
||||||
|
true -> { }
|
||||||
|
false -> { GuestSessionIcon() }
|
||||||
|
null -> { NoSessionAccountIcon() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { expanded ->
|
||||||
|
when(session?.isAuthorized) {
|
||||||
|
true -> { AuthorizedSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
|
||||||
|
false -> { GuestSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
|
||||||
|
null -> { NoSessionMenuItems(expanded = expanded, lastSelectedOption = lastSelectedOption) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoSessionMenuItems(
|
||||||
|
expanded: MutableState<Boolean>,
|
||||||
|
lastSelectedOption: MutableState<String>
|
||||||
|
) {
|
||||||
|
val showSignInDialog = remember { mutableStateOf(false) }
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
showSignInDialog.value = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.background(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_sign_in), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSignInDialog.value) {
|
||||||
|
SignInDialog(showDialog = showSignInDialog) { success ->
|
||||||
|
if (success) {
|
||||||
|
lastSelectedOption.value = NO_SESSION_SIGN_IN
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider(color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = 12.dp))
|
||||||
|
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
createGuestSession(lastSelectedOption)
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_sign_in_as_guest), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoSessionAccountIcon() {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(50.dp)
|
||||||
|
.padding(end = 8.dp),
|
||||||
|
imageVector = Icons.Filled.AccountCircle,
|
||||||
|
contentDescription = stringResource(R.string.account_menu_content_description),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GuestSessionMenuItems(
|
||||||
|
expanded: MutableState<Boolean>,
|
||||||
|
lastSelectedOption: MutableState<String>
|
||||||
|
) {
|
||||||
|
val showSignInDialog = remember { mutableStateOf(false) }
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
showSignInDialog.value = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.background(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.action_sign_in), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSignInDialog.value) {
|
||||||
|
SignInDialog(showDialog = showSignInDialog) { success ->
|
||||||
|
if (success) {
|
||||||
|
lastSelectedOption.value = GUEST_SIGN_IN
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider(color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = 12.dp))
|
||||||
|
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
signOut(lastSelectedOption)
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.action_sign_out), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GuestSessionIcon() {
|
||||||
|
val guestName = stringResource(id = R.string.account_name_guest)
|
||||||
|
RoundedLetterImage(size = 40.dp, character = guestName[0], modifier = Modifier.padding(end = 8.dp), topPadding = 40.dp / 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AuthorizedSessionMenuItems(
|
||||||
|
expanded: MutableState<Boolean>,
|
||||||
|
lastSelectedOption: MutableState<String>
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
lastSelectedOption.value = ACCOUNT_SIGN_OUT
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.action_sign_out), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>) {
|
||||||
|
SessionManager.clearSession { isSuccessful ->
|
||||||
|
if (isSuccessful) {
|
||||||
|
lastSelectedOption.value = SIGN_OUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPagerApi::class)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.owenlejeune.tvtime.utils
|
package com.owenlejeune.tvtime.utils
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.DeleteSessionBody
|
import com.owenlejeune.tvtime.api.tmdb.model.*
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.RatedEpisode
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.RatedMovie
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.RatedTv
|
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -27,13 +24,14 @@ object SessionManager: KoinComponent {
|
|||||||
currentSession?.let { session ->
|
currentSession?.let { session ->
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val deleteResponse = authenticationService.deleteSession(
|
val deleteResponse = authenticationService.deleteSession(
|
||||||
DeleteSessionBody(
|
SessionBody(
|
||||||
session.sessionId
|
session.sessionId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (deleteResponse.isSuccessful) {
|
if (deleteResponse.isSuccessful) {
|
||||||
_currentSession = null
|
_currentSession = null
|
||||||
|
preferences.guestSessionId = ""
|
||||||
}
|
}
|
||||||
onResponse(deleteResponse.isSuccessful)
|
onResponse(deleteResponse.isSuccessful)
|
||||||
}
|
}
|
||||||
@@ -46,6 +44,8 @@ object SessionManager: KoinComponent {
|
|||||||
val session = GuestSession()
|
val session = GuestSession()
|
||||||
session.initialize()
|
session.initialize()
|
||||||
_currentSession = session
|
_currentSession = session
|
||||||
|
} else if (preferences.authorizedSessionId.isNotEmpty()) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,29 @@ object SessionManager: KoinComponent {
|
|||||||
return _currentSession
|
return _currentSession
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Session(val sessionId: String, val isGuest: Boolean) {
|
suspend fun signInWithLogin(email: String, password: String): Boolean {
|
||||||
|
val service = TmdbClient().createAuthenticationService()
|
||||||
|
val createTokenResponse = service.createRequestToken()
|
||||||
|
if (createTokenResponse.isSuccessful) {
|
||||||
|
createTokenResponse.body()?.let { ctr ->
|
||||||
|
val body = TokenValidationBody(email, password, ctr.requestToken)
|
||||||
|
val loginResponse = service.validateTokenWithLogin(body)
|
||||||
|
if (loginResponse.isSuccessful) {
|
||||||
|
loginResponse.body()?.let { lr ->
|
||||||
|
if (lr.success) {
|
||||||
|
preferences.authorizedSessionId = lr.requestToken
|
||||||
|
_currentSession = AuthorizedSession()
|
||||||
|
_currentSession?.initialize()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Session(val sessionId: String, val isAuthorized: Boolean) {
|
||||||
protected abstract var _ratedMovies: List<RatedMovie>
|
protected abstract var _ratedMovies: List<RatedMovie>
|
||||||
val ratedMovies: List<RatedMovie>
|
val ratedMovies: List<RatedMovie>
|
||||||
get() = _ratedMovies
|
get() = _ratedMovies
|
||||||
@@ -88,7 +110,21 @@ object SessionManager: KoinComponent {
|
|||||||
abstract suspend fun refresh()
|
abstract suspend fun refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GuestSession: Session(preferences.guestSessionId, true) {
|
private class AuthorizedSession: Session(preferences.authorizedSessionId, true) {
|
||||||
|
override var _ratedMovies: List<RatedMovie> = emptyList()
|
||||||
|
override var _ratedTvShows: List<RatedTv> = emptyList()
|
||||||
|
override var _ratedTvEpisodes: List<RatedEpisode> = emptyList()
|
||||||
|
|
||||||
|
override suspend fun initialize() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun refresh() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GuestSession: Session(preferences.guestSessionId, false) {
|
||||||
override var _ratedMovies: List<RatedMovie> = emptyList()
|
override var _ratedMovies: List<RatedMovie> = emptyList()
|
||||||
override var _ratedTvShows: List<RatedTv> = emptyList()
|
override var _ratedTvShows: List<RatedTv> = emptyList()
|
||||||
override var _ratedTvEpisodes: List<RatedEpisode> = emptyList()
|
override var _ratedTvEpisodes: List<RatedEpisode> = emptyList()
|
||||||
|
|||||||
@@ -77,4 +77,22 @@
|
|||||||
<string name="status_ended">Ended</string>
|
<string name="status_ended">Ended</string>
|
||||||
<string name="status_pilot">Pilot</string>
|
<string name="status_pilot">Pilot</string>
|
||||||
<string name="status_active">Active</string>
|
<string name="status_active">Active</string>
|
||||||
|
|
||||||
|
<!-- account -->
|
||||||
|
<string name="account_header_title_formatted">Hello, %1$s!</string>
|
||||||
|
<string name="account_name_guest">Guest</string>
|
||||||
|
<string name="account_not_logged_in">Not logged in</string>
|
||||||
|
|
||||||
|
<string name="no_rated_content_message">No rated content</string>
|
||||||
|
<string name="rating_test">Rating: $1%d</string>
|
||||||
|
<string name="action_sign_in_as_guest">Sign In as Guest</string>
|
||||||
|
<string name="account_menu_content_description">Account Menu</string>
|
||||||
|
<string name="action_sign_out">Sign Out</string>
|
||||||
|
<string name="email_not_empty_error">Email must not be empty</string>
|
||||||
|
<string name="email_invalid_error">Invalid email</string>
|
||||||
|
<string name="password_empty_error">Must enter a password</string>
|
||||||
|
<string name="sign_in_dialog_message">Sign in using your TMDB credentials</string>
|
||||||
|
<string name="email_label">Email</string>
|
||||||
|
<string name="password_label">Password</string>
|
||||||
|
<string name="no_account_message">Don\'t have an account?</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -3,7 +3,7 @@ package com.owenlejeune.tvtime.buildsrc
|
|||||||
object Versions {
|
object Versions {
|
||||||
|
|
||||||
const val compose = "1.1.0-rc03"
|
const val compose = "1.1.0-rc03"
|
||||||
const val compose_material3 = "1.0.0-alpha04"
|
const val compose_material3 = "1.0.0-alpha06"
|
||||||
const val compose_accompanist = "0.22.1-rc"
|
const val compose_accompanist = "0.22.1-rc"
|
||||||
const val compose_navigation = "2.4.0"
|
const val compose_navigation = "2.4.0"
|
||||||
const val compose_paging = "1.0.0-alpha14"
|
const val compose_paging = "1.0.0-alpha14"
|
||||||
|
|||||||
Reference in New Issue
Block a user