mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-21 19:20:57 -05:00
sign in and display account content
This commit is contained in:
@@ -19,7 +19,7 @@ interface AuthenticationApi {
|
|||||||
suspend fun createRequestToken(): Response<CreateTokenResponse>
|
suspend fun createRequestToken(): Response<CreateTokenResponse>
|
||||||
|
|
||||||
@POST("authentication/session/new")
|
@POST("authentication/session/new")
|
||||||
suspend fun createSession(@Body body: SessionBody): Response<CreateSessionResponse>
|
suspend fun createSession(@Body body: TokenSessionBody): Response<CreateSessionResponse>
|
||||||
|
|
||||||
@POST("authentication/token/validate_with_login")
|
@POST("authentication/token/validate_with_login")
|
||||||
suspend fun validateTokenWithLogin(@Body body: TokenValidationBody): Response<CreateTokenResponse>
|
suspend fun validateTokenWithLogin(@Body body: TokenValidationBody): Response<CreateTokenResponse>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.SessionBody
|
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
|
||||||
|
|
||||||
class AuthenticationService {
|
class AuthenticationService {
|
||||||
@@ -17,4 +15,15 @@ class AuthenticationService {
|
|||||||
return service.deleteSession(body)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.owenlejeune.tvtime.BuildConfig
|
|||||||
import com.owenlejeune.tvtime.api.Client
|
import com.owenlejeune.tvtime.api.Client
|
||||||
import com.owenlejeune.tvtime.api.QueryParam
|
import com.owenlejeune.tvtime.api.QueryParam
|
||||||
import com.owenlejeune.tvtime.extensions.addQueryParams
|
import com.owenlejeune.tvtime.extensions.addQueryParams
|
||||||
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@@ -19,6 +20,7 @@ class TmdbClient: KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val client: Client by inject { parametersOf(BASE_URL) }
|
private val client: Client by inject { parametersOf(BASE_URL) }
|
||||||
|
private val preferences: AppPreferences by inject()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
client.addInterceptor(TmdbInterceptor())
|
client.addInterceptor(TmdbInterceptor())
|
||||||
@@ -56,11 +58,8 @@ class TmdbClient: KoinComponent {
|
|||||||
val languageCode = "${locale.language}-${locale.region}"
|
val languageCode = "${locale.language}-${locale.region}"
|
||||||
val languageParam = QueryParam("language", languageCode)
|
val languageParam = QueryParam("language", languageCode)
|
||||||
|
|
||||||
var sessionIdParam: QueryParam? = null
|
val segments = chain.request().url.encodedPathSegments
|
||||||
val segments = chain.request().url().encodedPathSegments()
|
val sessionIdParam: QueryParam? = sessionIdParam(segments)
|
||||||
if (segments.size > 1 && segments[1].equals("account") && SessionManager.currentSession?.isAuthorized == true) {
|
|
||||||
sessionIdParam = QueryParam("session_id", SessionManager.currentSession!!.sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = chain.addQueryParams(apiParam, languageParam, sessionIdParam)
|
val request = chain.addQueryParams(apiParam, languageParam, sessionIdParam)
|
||||||
|
|
||||||
@@ -68,4 +67,16 @@ class TmdbClient: KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sessionIdParam(urlSegments: List<String>): QueryParam? {
|
||||||
|
var sessionIdParam: QueryParam? = null
|
||||||
|
if (urlSegments.size > 1 && urlSegments[1] == "account") {
|
||||||
|
if (SessionManager.currentSession?.isAuthorized == true) {
|
||||||
|
sessionIdParam = QueryParam("session_id", SessionManager.currentSession!!.sessionId)
|
||||||
|
} else if (preferences.authorizedSessionId.isNotEmpty()) {
|
||||||
|
sessionIdParam = QueryParam("session_id", preferences.authorizedSessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessionIdParam
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,19 @@ class AccountDetails(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Avatar(
|
class Avatar(
|
||||||
@SerializedName("gravatar") val gravatar: Gravatar
|
@SerializedName("gravatar") val gravatar: Gravatar?,
|
||||||
|
@SerializedName("tmdb") val tmdb: TmdbAvatar?
|
||||||
)
|
)
|
||||||
|
|
||||||
class Gravatar(
|
class Gravatar(
|
||||||
@SerializedName("hash") val hash: String
|
@SerializedName("hash") val hash: String?
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val DEF_HASH = "88c8a9052642ec51d85d4f7beb178a97"
|
||||||
|
}
|
||||||
|
fun isDefault() = hash?.equals(DEF_HASH)
|
||||||
|
}
|
||||||
|
|
||||||
|
class TmdbAvatar(
|
||||||
|
@SerializedName("avatar_path") val avatarPath: String?
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
class CreateSessionResponse(
|
class CreateSessionResponse(
|
||||||
@SerializedName("success") val isSuccess: Boolean,
|
@SerializedName("success") val isSuccess: Boolean,
|
||||||
@SerializedName("session_idd") val sessionId: String
|
@SerializedName("session_id") val sessionId: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ class RatedEpisode(
|
|||||||
voteAverage: Float,
|
voteAverage: Float,
|
||||||
voteCount: Int,
|
voteCount: Int,
|
||||||
rating: Float,
|
rating: Float,
|
||||||
@SerializedName("air_date") val airDate: String,
|
releaseDate: String,
|
||||||
@SerializedName("episode_number") val episodeNumber: Int,
|
@SerializedName("episode_number") val episodeNumber: Int,
|
||||||
@SerializedName("production_code") val productionCode: String?,
|
@SerializedName("production_code") val productionCode: String?,
|
||||||
@SerializedName("season_number") val seasonNumber: Int,
|
@SerializedName("season_number") val seasonNumber: Int,
|
||||||
@SerializedName("show_id") val showId: Int,
|
@SerializedName("show_id") val showId: Int,
|
||||||
@SerializedName("still_path") val stillPath: String?,
|
@SerializedName("still_path") val stillPath: String?,
|
||||||
): RatedMedia(RatedType.EPISODE, id, overview, name, voteAverage, voteCount, rating)
|
): RatedMedia(RatedType.EPISODE, id, overview, name, voteAverage, voteCount, rating, releaseDate)
|
||||||
@@ -9,7 +9,8 @@ abstract class RatedMedia(
|
|||||||
@SerializedName("name", alternate = ["title"]) val name: String,
|
@SerializedName("name", alternate = ["title"]) val name: String,
|
||||||
@SerializedName("vote_average") val voteAverage: Float,
|
@SerializedName("vote_average") val voteAverage: Float,
|
||||||
@SerializedName("vote_count") val voteCount: Int,
|
@SerializedName("vote_count") val voteCount: Int,
|
||||||
@SerializedName("rating") val rating: Float
|
@SerializedName("rating") val rating: Float,
|
||||||
|
@SerializedName("release_date", alternate = ["first_air_date", "air_date"]) val releaseDate: String
|
||||||
) {
|
) {
|
||||||
enum class RatedType {
|
enum class RatedType {
|
||||||
MOVIE,
|
MOVIE,
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ class RatedMovie(
|
|||||||
originalName: String,
|
originalName: String,
|
||||||
posterPath: String?,
|
posterPath: String?,
|
||||||
popularity: Float,
|
popularity: Float,
|
||||||
|
releaseDate: String,
|
||||||
@SerializedName("adult") val isAdult: Boolean,
|
@SerializedName("adult") val isAdult: Boolean,
|
||||||
@SerializedName("release_date") val releaseDate: String,
|
|
||||||
@SerializedName("video") val video: Boolean,
|
@SerializedName("video") val video: Boolean,
|
||||||
): RatedTopLevelMedia(
|
): RatedTopLevelMedia(
|
||||||
RatedType.MOVIE, id, overview, name, voteAverage, voteCount, rating,
|
RatedType.MOVIE, id, overview, name, voteAverage, voteCount, rating, releaseDate,
|
||||||
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
|
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
|
||||||
)
|
)
|
||||||
@@ -10,10 +10,11 @@ abstract class RatedTopLevelMedia(
|
|||||||
voteAverage: Float,
|
voteAverage: Float,
|
||||||
voteCount: Int,
|
voteCount: Int,
|
||||||
rating: Float,
|
rating: Float,
|
||||||
|
releaseDate: String,
|
||||||
@SerializedName("backdrop_path") val backdropPath: String?,
|
@SerializedName("backdrop_path") val backdropPath: String?,
|
||||||
@SerializedName("genre_ids") val genreIds: List<Int>,
|
@SerializedName("genre_ids") val genreIds: List<Int>,
|
||||||
@SerializedName("original_language") val originalLanguage: String,
|
@SerializedName("original_language") val originalLanguage: String,
|
||||||
@SerializedName("original_name", alternate = ["original_title"]) val originalName: String,
|
@SerializedName("original_name", alternate = ["original_title"]) val originalName: String,
|
||||||
@SerializedName("poster_path") val posterPath: String?,
|
@SerializedName("poster_path") val posterPath: String?,
|
||||||
@SerializedName("popularity") val popularity: Float,
|
@SerializedName("popularity") val popularity: Float,
|
||||||
): RatedMedia(type, id, overview, name, voteAverage, voteCount, rating)
|
): RatedMedia(type, id, overview, name, voteAverage, voteCount, rating, releaseDate)
|
||||||
@@ -15,9 +15,9 @@ class RatedTv(
|
|||||||
originalName: String,
|
originalName: String,
|
||||||
posterPath: String?,
|
posterPath: String?,
|
||||||
popularity: Float,
|
popularity: Float,
|
||||||
@SerializedName("first_air_date") val firstAirDate: String,
|
releaseDate: String,
|
||||||
@SerializedName("origin_country") val originCountry: List<String>,
|
@SerializedName("origin_country") val originCountry: List<String>,
|
||||||
): RatedTopLevelMedia(
|
): RatedTopLevelMedia(
|
||||||
RatedType.SERIES, id, overview, name, voteAverage, voteCount, rating,
|
RatedType.SERIES, id, overview, name, voteAverage, voteCount, rating, releaseDate,
|
||||||
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
|
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class TokenSessionBody(
|
||||||
|
@SerializedName("request_token") val requestToken: String
|
||||||
|
)
|
||||||
@@ -6,7 +6,7 @@ import okhttp3.Request
|
|||||||
|
|
||||||
fun Interceptor.Chain.addQueryParams(vararg queryParams: QueryParam?): Request {
|
fun Interceptor.Chain.addQueryParams(vararg queryParams: QueryParam?): Request {
|
||||||
val original = request()
|
val original = request()
|
||||||
val originalHttpUrl = original.url()
|
val originalHttpUrl = original.url
|
||||||
|
|
||||||
val urlBuilder = originalHttpUrl.newBuilder()
|
val urlBuilder = originalHttpUrl.newBuilder()
|
||||||
queryParams.forEach { param ->
|
queryParams.forEach { param ->
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ import android.util.Patterns
|
|||||||
fun String.isEmailValid(): Boolean {
|
fun String.isEmailValid(): Boolean {
|
||||||
return !TextUtils.isEmpty(this) && Patterns.EMAIL_ADDRESS.matcher(this).matches()
|
return !TextUtils.isEmpty(this) && Patterns.EMAIL_ADDRESS.matcher(this).matches()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.unlessEmpty(other: String): String {
|
||||||
|
return this.ifEmpty { other }
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ fun CustomTopAppBarDropdownMenu(
|
|||||||
) {
|
) {
|
||||||
val expanded = remember { mutableStateOf(false) }
|
val expanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Box(modifier = Modifier.wrapContentSize(Alignment.TopEnd)) {
|
Box(modifier = Modifier.wrapContentSize(Alignment.TopEnd).padding(end = 12.dp)) {
|
||||||
IconButton(onClick = { expanded.value = true }) {
|
IconButton(onClick = { expanded.value = true }) {
|
||||||
icon()
|
icon()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,31 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.size
|
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.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
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.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.extensions.isEmailValid
|
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
|
@Composable
|
||||||
fun SignInDialog(
|
fun SignInDialog(
|
||||||
showDialog: MutableState<Boolean>,
|
showDialog: MutableState<Boolean>,
|
||||||
@@ -27,33 +41,34 @@ fun SignInDialog(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var emailState by rememberSaveable { mutableStateOf("") }
|
var usernameState by rememberSaveable { mutableStateOf("") }
|
||||||
var emailHasErrors by rememberSaveable { mutableStateOf(false) }
|
var usernameHasErrors by rememberSaveable { mutableStateOf(false) }
|
||||||
var emailError = ""
|
var usernameError = ""
|
||||||
|
|
||||||
var passwordState by rememberSaveable { mutableStateOf("") }
|
var passwordState by rememberSaveable { mutableStateOf("") }
|
||||||
var passwordHasErrors by rememberSaveable { mutableStateOf(false) }
|
var passwordHasErrors by rememberSaveable { mutableStateOf(false) }
|
||||||
var passwordError = ""
|
var passwordError = ""
|
||||||
|
|
||||||
fun validate(): Boolean {
|
fun validate(): Boolean {
|
||||||
emailError = ""
|
usernameError = ""
|
||||||
passwordError = ""
|
passwordError = ""
|
||||||
|
|
||||||
if (TextUtils.isEmpty(emailState)) {
|
if (TextUtils.isEmpty(usernameState)) {
|
||||||
emailError = context.getString(R.string.email_not_empty_error)
|
usernameError = context.getString(R.string.username_not_empty_error)
|
||||||
} else if (!emailState.isEmailValid()) {
|
|
||||||
emailError = context.getString(R.string.email_invalid_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(passwordState)) {
|
if (TextUtils.isEmpty(passwordState)) {
|
||||||
passwordError = context.getString(R.string.password_empty_error)
|
passwordError = context.getString(R.string.password_empty_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
emailHasErrors = emailError.isNotEmpty()
|
usernameHasErrors = usernameError.isNotEmpty()
|
||||||
passwordHasErrors = passwordError.isNotEmpty()
|
passwordHasErrors = passwordError.isNotEmpty()
|
||||||
|
|
||||||
return !emailHasErrors && !passwordHasErrors
|
return !usernameHasErrors && !passwordHasErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = { Text(text = stringResource(R.string.action_sign_in)) },
|
title = { Text(text = stringResource(R.string.action_sign_in)) },
|
||||||
onDismissRequest = { showDialog.value = false },
|
onDismissRequest = { showDialog.value = false },
|
||||||
@@ -66,14 +81,15 @@ fun SignInDialog(
|
|||||||
text = stringResource(R.string.sign_in_dialog_message)
|
text = stringResource(R.string.sign_in_dialog_message)
|
||||||
)
|
)
|
||||||
ThemedOutlineTextField(
|
ThemedOutlineTextField(
|
||||||
value = emailState,
|
value = usernameState,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
emailHasErrors = false
|
usernameHasErrors = false
|
||||||
emailState = it
|
usernameState = it
|
||||||
},
|
},
|
||||||
label = { Text(text = stringResource(R.string.email_label)) },
|
label = { Text(text = stringResource(R.string.username_label)) },
|
||||||
isError = emailHasErrors,
|
isError = usernameHasErrors,
|
||||||
errorMessage = emailError
|
errorMessage = usernameError,
|
||||||
|
singleLine = true
|
||||||
)
|
)
|
||||||
PasswordOutlineTextField(
|
PasswordOutlineTextField(
|
||||||
value = passwordState,
|
value = passwordState,
|
||||||
@@ -83,9 +99,10 @@ fun SignInDialog(
|
|||||||
},
|
},
|
||||||
label = { Text(text = stringResource(R.string.password_label)) },
|
label = { Text(text = stringResource(R.string.password_label)) },
|
||||||
isError = passwordHasErrors,
|
isError = passwordHasErrors,
|
||||||
errorMessage = passwordError
|
errorMessage = passwordError,
|
||||||
|
singleLine = true
|
||||||
)
|
)
|
||||||
SignInButton(validate = ::validate) { success ->
|
SignInButton(username = usernameState, password = passwordState, validate = ::validate) { success ->
|
||||||
if (success) {
|
if (success) {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
} else {
|
} else {
|
||||||
@@ -107,7 +124,7 @@ private fun CancelButton(showDialog: MutableState<Boolean>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SignInButton(validate: () -> Boolean, onSuccess: (success: Boolean) -> Unit) {
|
private fun SignInButton(username: String, password: String, validate: () -> Boolean, onSuccess: (success: Boolean) -> Unit) {
|
||||||
var signInInProgress by remember { mutableStateOf(false) }
|
var signInInProgress by remember { mutableStateOf(false) }
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -115,13 +132,13 @@ private fun SignInButton(validate: () -> Boolean, onSuccess: (success: Boolean)
|
|||||||
if (!signInInProgress) {
|
if (!signInInProgress) {
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
signInInProgress = true
|
signInInProgress = true
|
||||||
// signIn(context, emailState, passwordState) { success ->
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
// signInInProgress = false
|
val success = SessionManager.signInWithLogin(username, password)
|
||||||
// if (success) {
|
withContext(Dispatchers.Main) {
|
||||||
// showDialog.value = false
|
signInInProgress = false
|
||||||
// }
|
onSuccess(success)
|
||||||
// }
|
}
|
||||||
onSuccess(false)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +150,7 @@ private fun SignInButton(validate: () -> Boolean, onSuccess: (success: Boolean)
|
|||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(text = stringResource(id = R.string.action_sign_in))
|
Text(text = stringResource(id = R.string.action_sign_in), color = MaterialTheme.colorScheme.background)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -794,6 +794,7 @@ fun PasswordOutlineTextField(
|
|||||||
isError: Boolean = false,
|
isError: Boolean = false,
|
||||||
errorMessage: String = "",
|
errorMessage: String = "",
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
singleLine: Boolean = false,
|
singleLine: Boolean = false,
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
@@ -818,7 +819,7 @@ fun PasswordOutlineTextField(
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
shape = shape,
|
shape = shape,
|
||||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
keyboardOptions = keyboardOptions.copy(keyboardType = KeyboardType.Password),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
val image = if (passwordVisible) {
|
val image = if (passwordVisible) {
|
||||||
Icons.Filled.Visibility
|
Icons.Filled.Visibility
|
||||||
|
|||||||
@@ -622,7 +622,7 @@ private fun ReviewsCard(
|
|||||||
fetchReviews(itemId, service, reviewsResponse)
|
fetchReviews(itemId, service, reviewsResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// > 0
|
|
||||||
val hasReviews = reviewsResponse.value?.results?.size?.let { it > 0 }
|
val hasReviews = reviewsResponse.value?.results?.size?.let { it > 0 }
|
||||||
val m = if (hasReviews == true) {
|
val m = if (hasReviews == true) {
|
||||||
modifier.height(400.dp)
|
modifier.height(400.dp)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import androidx.compose.foundation.Image
|
|||||||
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.foundation.shape.CircleShape
|
||||||
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.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -14,12 +15,18 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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
|
||||||
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.rememberImagePainter
|
import coil.compose.rememberImagePainter
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.request.ImageResult
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
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
|
||||||
@@ -42,7 +49,6 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
private const val GUEST_SIGN_IN = "guest_sign_in"
|
private const val GUEST_SIGN_IN = "guest_sign_in"
|
||||||
private const val SIGN_OUT = "sign_out"
|
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 = "no_session_sign_in"
|
||||||
private const val NO_SESSION_SIGN_IN_GUEST = "no_session_sign_in_guest"
|
private const val NO_SESSION_SIGN_IN_GUEST = "no_session_sign_in_guest"
|
||||||
|
|
||||||
@@ -53,10 +59,18 @@ fun AccountTab(
|
|||||||
appBarTitle: MutableState<String>,
|
appBarTitle: MutableState<String>,
|
||||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
|
||||||
) {
|
) {
|
||||||
if (SessionManager.currentSession?.isAuthorized == false) {
|
when (SessionManager.currentSession?.isAuthorized) {
|
||||||
appBarTitle.value = stringResource(id = R.string.account_header_title_formatted).replace("%1\$s", stringResource(id = R.string.account_name_guest))
|
false -> {
|
||||||
} else {
|
appBarTitle.value =
|
||||||
appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
|
stringResource(id = R.string.account_header_title_formatted, stringResource(id = R.string.account_name_guest))
|
||||||
|
}
|
||||||
|
true -> {
|
||||||
|
appBarTitle.value =
|
||||||
|
stringResource(id = R.string.account_header_title_formatted, getAccountName(SessionManager.currentSession?.accountDetails))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastSelectedOption = remember { mutableStateOf("") }
|
val lastSelectedOption = remember { mutableStateOf("") }
|
||||||
@@ -86,6 +100,17 @@ fun AccountTab(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getAccountName(accountDetails: AccountDetails?): String {
|
||||||
|
if (accountDetails != null) {
|
||||||
|
if (accountDetails.name.isNotEmpty()) {
|
||||||
|
return accountDetails.name
|
||||||
|
} else if (accountDetails.username.isNotEmpty()) {
|
||||||
|
return accountDetails.username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T: Any> AccountTabContent(
|
fun <T: Any> AccountTabContent(
|
||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
@@ -113,8 +138,8 @@ fun <T: Any> AccountTabContent(
|
|||||||
} else {
|
} else {
|
||||||
items(contentItems.size) { i ->
|
items(contentItems.size) { i ->
|
||||||
when (clazz) {
|
when (clazz) {
|
||||||
RatedMovie::class -> {
|
RatedTv::class, RatedMovie::class -> {
|
||||||
val item = contentItems[i] as RatedMovie
|
val item = contentItems[i] as RatedTopLevelMedia
|
||||||
MediaItemRow(
|
MediaItemRow(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
mediaViewType = mediaViewType,
|
mediaViewType = mediaViewType,
|
||||||
@@ -125,32 +150,19 @@ fun <T: Any> AccountTabContent(
|
|||||||
rating = item.rating
|
rating = item.rating
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RatedTv::class -> {
|
|
||||||
val item = contentItems[i] as RatedTv
|
|
||||||
MediaItemRow(
|
|
||||||
appNavController = appNavController,
|
|
||||||
mediaViewType = mediaViewType,
|
|
||||||
id = item.id,
|
|
||||||
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
|
|
||||||
name = item.name,
|
|
||||||
date = item.firstAirDate,
|
|
||||||
rating = item.rating
|
|
||||||
)
|
|
||||||
}
|
|
||||||
RatedEpisode::class -> {
|
RatedEpisode::class -> {
|
||||||
val item = contentItems[i] as RatedEpisode
|
val item = contentItems[i] as RatedMedia
|
||||||
MediaItemRow(
|
MediaItemRow(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
mediaViewType = mediaViewType,
|
mediaViewType = mediaViewType,
|
||||||
id = item.id,
|
id = item.id,
|
||||||
posterPath = null,
|
|
||||||
name = item.name,
|
name = item.name,
|
||||||
date = item.airDate,
|
date = item.releaseDate,
|
||||||
rating = item.rating
|
rating = item.rating
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
FavoriteMovie::class -> {
|
FavoriteMovie::class, FavoriteTvSeries::class -> {
|
||||||
val item = contentItems[i] as FavoriteMovie
|
val item = contentItems[i] as FavoriteMedia
|
||||||
MediaItemRow(
|
MediaItemRow(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
mediaViewType = mediaViewType,
|
mediaViewType = mediaViewType,
|
||||||
@@ -160,30 +172,8 @@ fun <T: Any> AccountTabContent(
|
|||||||
date = item.releaseDate
|
date = item.releaseDate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
FavoriteTvSeries::class -> {
|
WatchlistMovie::class, WatchlistTvSeries::class -> {
|
||||||
val item = contentItems[i] as FavoriteTvSeries
|
val item = contentItems[i] as WatchlistMedia
|
||||||
MediaItemRow(
|
|
||||||
appNavController = appNavController,
|
|
||||||
mediaViewType = mediaViewType,
|
|
||||||
id = item.id,
|
|
||||||
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
|
|
||||||
name = item.title,
|
|
||||||
date = item.releaseDate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
WatchlistMovie::class -> {
|
|
||||||
val item = contentItems[i] as WatchlistMovie
|
|
||||||
MediaItemRow(
|
|
||||||
appNavController = appNavController,
|
|
||||||
mediaViewType = mediaViewType,
|
|
||||||
id = item.id,
|
|
||||||
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
|
|
||||||
name = item.title,
|
|
||||||
date = item.releaseDate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
WatchlistTvSeries::class -> {
|
|
||||||
val item = contentItems[i] as WatchlistTvSeries
|
|
||||||
MediaItemRow(
|
MediaItemRow(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
mediaViewType = mediaViewType,
|
mediaViewType = mediaViewType,
|
||||||
@@ -204,9 +194,9 @@ private fun MediaItemRow(
|
|||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
mediaViewType: MediaViewType,
|
mediaViewType: MediaViewType,
|
||||||
id: Int,
|
id: Int,
|
||||||
posterPath: String?,
|
|
||||||
name: String,
|
name: String,
|
||||||
date: String,
|
date: String,
|
||||||
|
posterPath: String? = null,
|
||||||
rating: Float? = null
|
rating: Float? = null
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -262,7 +252,7 @@ private fun AccountDropdownMenu(
|
|||||||
CustomTopAppBarDropdownMenu(
|
CustomTopAppBarDropdownMenu(
|
||||||
icon = {
|
icon = {
|
||||||
when(session?.isAuthorized) {
|
when(session?.isAuthorized) {
|
||||||
true -> { }
|
true -> { AuthorizedSessionIcon() }
|
||||||
false -> { GuestSessionIcon() }
|
false -> { GuestSessionIcon() }
|
||||||
null -> { NoSessionAccountIcon() }
|
null -> { NoSessionAccountIcon() }
|
||||||
}
|
}
|
||||||
@@ -312,9 +302,7 @@ private fun NoSessionMenuItems(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun NoSessionAccountIcon() {
|
private fun NoSessionAccountIcon() {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier
|
modifier = Modifier.size(45.dp),
|
||||||
.size(50.dp)
|
|
||||||
.padding(end = 8.dp),
|
|
||||||
imageVector = Icons.Filled.AccountCircle,
|
imageVector = Icons.Filled.AccountCircle,
|
||||||
contentDescription = stringResource(R.string.account_menu_content_description),
|
contentDescription = stringResource(R.string.account_menu_content_description),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
@@ -366,12 +354,43 @@ private fun AuthorizedSessionMenuItems(
|
|||||||
CustomMenuItem(
|
CustomMenuItem(
|
||||||
text = stringResource(id = R.string.action_sign_out),
|
text = stringResource(id = R.string.action_sign_out),
|
||||||
onClick = {
|
onClick = {
|
||||||
lastSelectedOption.value = ACCOUNT_SIGN_OUT
|
signOut(lastSelectedOption)
|
||||||
expanded.value = false
|
expanded.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AuthorizedSessionIcon() {
|
||||||
|
val accountDetails = SessionManager.currentSession?.accountDetails
|
||||||
|
val avatarUrl = accountDetails?.let {
|
||||||
|
when {
|
||||||
|
accountDetails.avatar.tmdb?.avatarPath?.isNotEmpty() == true -> {
|
||||||
|
TmdbUtils.getAccountAvatarUrl(accountDetails)
|
||||||
|
}
|
||||||
|
accountDetails.avatar.gravatar?.isDefault() == false -> {
|
||||||
|
TmdbUtils.getAccountGravatarUrl(accountDetails)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (accountDetails == null || avatarUrl == null) {
|
||||||
|
val accLetter = (accountDetails?.name?.ifEmpty { accountDetails.username } ?: " ")[0]
|
||||||
|
RoundedLetterImage(size = 40.dp, character = accLetter, topPadding = 40.dp / 8)
|
||||||
|
} else {
|
||||||
|
Box(modifier = Modifier.size(50.dp)) {
|
||||||
|
AsyncImage(
|
||||||
|
model = avatarUrl,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createGuestSession(lastSelectedOption: MutableState<String>) {
|
private fun createGuestSession(lastSelectedOption: MutableState<String>) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val session = SessionManager.requestNewGuestSession()
|
val session = SessionManager.requestNewGuestSession()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.owenlejeune.tvtime.utils
|
package com.owenlejeune.tvtime.utils
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.AccountService
|
import com.owenlejeune.tvtime.api.tmdb.AccountService
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.AuthenticationService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.GuestSessionService
|
import com.owenlejeune.tvtime.api.tmdb.GuestSessionService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.*
|
import com.owenlejeune.tvtime.api.tmdb.model.*
|
||||||
@@ -25,11 +26,7 @@ object SessionManager: KoinComponent {
|
|||||||
fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) {
|
fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) {
|
||||||
currentSession?.let { session ->
|
currentSession?.let { session ->
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val deleteResponse = authenticationService.deleteSession(
|
val deleteResponse = authenticationService.deleteSession(SessionBody(session.sessionId))
|
||||||
SessionBody(
|
|
||||||
session.sessionId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (deleteResponse.isSuccessful) {
|
if (deleteResponse.isSuccessful) {
|
||||||
_currentSession = null
|
_currentSession = null
|
||||||
@@ -48,7 +45,9 @@ object SessionManager: KoinComponent {
|
|||||||
session.initialize()
|
session.initialize()
|
||||||
_currentSession = session
|
_currentSession = session
|
||||||
} else if (preferences.authorizedSessionId.isNotEmpty()) {
|
} else if (preferences.authorizedSessionId.isNotEmpty()) {
|
||||||
|
val session = AuthorizedSession()
|
||||||
|
session.initialize()
|
||||||
|
_currentSession = session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,20 +60,29 @@ object SessionManager: KoinComponent {
|
|||||||
return _currentSession
|
return _currentSession
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun signInWithLogin(email: String, password: String): Boolean {
|
suspend fun signInWithLogin(username: String, password: String): Boolean {
|
||||||
val service = TmdbClient().createAuthenticationService()
|
val service = AuthenticationService()
|
||||||
val createTokenResponse = service.createRequestToken()
|
val createTokenResponse = service.createRequestToken()
|
||||||
if (createTokenResponse.isSuccessful) {
|
if (createTokenResponse.isSuccessful) {
|
||||||
createTokenResponse.body()?.let { ctr ->
|
createTokenResponse.body()?.let { ctr ->
|
||||||
val body = TokenValidationBody(email, password, ctr.requestToken)
|
val body = TokenValidationBody(username, password, ctr.requestToken)
|
||||||
val loginResponse = service.validateTokenWithLogin(body)
|
val loginResponse = service.validateTokenWithLogin(body)
|
||||||
if (loginResponse.isSuccessful) {
|
if (loginResponse.isSuccessful) {
|
||||||
loginResponse.body()?.let { lr ->
|
loginResponse.body()?.let { lr ->
|
||||||
if (lr.success) {
|
if (lr.success) {
|
||||||
preferences.authorizedSessionId = lr.requestToken
|
val sessionBody = TokenSessionBody(lr.requestToken)
|
||||||
_currentSession = AuthorizedSession()
|
val sessionResponse = service.createSession(sessionBody)
|
||||||
_currentSession?.initialize()
|
if (sessionResponse.isSuccessful) {
|
||||||
return true
|
sessionResponse.body()?.let { sr ->
|
||||||
|
if (sr.isSuccess) {
|
||||||
|
preferences.authorizedSessionId = sr.sessionId
|
||||||
|
preferences.guestSessionId = ""
|
||||||
|
_currentSession = AuthorizedSession()
|
||||||
|
_currentSession?.initialize()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ object TmdbUtils {
|
|||||||
private const val POSTER_BASE = "https://image.tmdb.org/t/p/original"
|
private const val POSTER_BASE = "https://image.tmdb.org/t/p/original"
|
||||||
private const val BACKDROP_BASE = "https://www.themoviedb.org/t/p/original"
|
private const val BACKDROP_BASE = "https://www.themoviedb.org/t/p/original"
|
||||||
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
||||||
|
private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/"
|
||||||
|
private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
|
||||||
|
|
||||||
private const val DEF_REGION = "US"
|
private const val DEF_REGION = "US"
|
||||||
|
|
||||||
fun getFullPosterPath(posterPath: String?): String? {
|
fun getFullPosterPath(posterPath: String?): String? {
|
||||||
return posterPath?.let { "https://image.tmdb.org/t/p/original${posterPath}" }
|
return posterPath?.let { "${POSTER_BASE}${posterPath}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFullPosterPath(tmdbItem: TmdbItem?): String? {
|
fun getFullPosterPath(tmdbItem: TmdbItem?): String? {
|
||||||
@@ -25,7 +27,7 @@ object TmdbUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getFullBackdropPath(backdropPath: String?): String? {
|
fun getFullBackdropPath(backdropPath: String?): String? {
|
||||||
return backdropPath?.let { "https://www.themoviedb.org/t/p/original${backdropPath}" }
|
return backdropPath?.let { "${BACKDROP_BASE}${backdropPath}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFullBackdropPath(detailItem: DetailedItem?): String? {
|
fun getFullBackdropPath(detailItem: DetailedItem?): String? {
|
||||||
@@ -37,7 +39,7 @@ object TmdbUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getFullPersonImagePath(path: String?): String? {
|
fun getFullPersonImagePath(path: String?): String? {
|
||||||
return path?.let { "https://www.themoviedb.org/t/p/w600_and_h900_bestv2${path}" }
|
return path?.let { "${PERSON_BASE}${path}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFullPersonImagePath(person: Person): String? {
|
fun getFullPersonImagePath(person: Person): String? {
|
||||||
@@ -49,7 +51,7 @@ object TmdbUtils {
|
|||||||
if (path.contains("http")) {
|
if (path.contains("http")) {
|
||||||
return path.substring(startIndex = 1)
|
return path.substring(startIndex = 1)
|
||||||
}
|
}
|
||||||
"https://www.themoviedb.org/t/p/w150_and_h150_face${path}"
|
"${AVATAR_BASE}${path}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,4 +164,14 @@ object TmdbUtils {
|
|||||||
return formatter.format(date)
|
return formatter.format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAccountGravatarUrl(accountDetails: AccountDetails): String {
|
||||||
|
val hash = accountDetails.avatar.gravatar?.hash
|
||||||
|
return "${GRAVATAR_BASE}${hash}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAccountAvatarUrl(accountDetails: AccountDetails): String {
|
||||||
|
val path = accountDetails.avatar.tmdb?.avatarPath
|
||||||
|
return "${AVATAR_BASE}${path}"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
<string name="action_sign_in_as_guest">Sign In as Guest</string>
|
<string name="action_sign_in_as_guest">Sign In as Guest</string>
|
||||||
<string name="account_menu_content_description">Account Menu</string>
|
<string name="account_menu_content_description">Account Menu</string>
|
||||||
<string name="action_sign_out">Sign Out</string>
|
<string name="action_sign_out">Sign Out</string>
|
||||||
<string name="email_not_empty_error">Email must not be empty</string>
|
<string name="username_not_empty_error">Username must not be empty</string>
|
||||||
<string name="email_invalid_error">Invalid email</string>
|
<string name="email_invalid_error">Invalid email</string>
|
||||||
<string name="password_empty_error">Must enter a password</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="sign_in_dialog_message">Sign in using your TMDB credentials</string>
|
||||||
<string name="email_label">Email</string>
|
<string name="username_label">Username</string>
|
||||||
<string name="password_label">Password</string>
|
<string name="password_label">Password</string>
|
||||||
<string name="no_account_message">Don\'t have an account?</string>
|
<string name="no_account_message">Don\'t have an account?</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -22,6 +22,6 @@ object Versions {
|
|||||||
const val gson = "2.8.7"
|
const val gson = "2.8.7"
|
||||||
const val koin = "3.1.4"
|
const val koin = "3.1.4"
|
||||||
const val paging = "3.1.0"
|
const val paging = "3.1.0"
|
||||||
const val coil = "1.4.0"
|
const val coil = "2.0.0-rc01"
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user