Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 31 additions & 24 deletions app/src/main/kotlin/com/arflix/tv/data/repository/AuthRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import com.arflix.tv.R
import com.arflix.tv.util.AppLogger
import com.arflix.tv.util.Constants
import com.arflix.tv.util.AuthEmailValidator
Expand Down Expand Up @@ -536,7 +537,8 @@ class AuthRepository @Inject constructor(
*/
suspend fun signIn(email: String, password: String): Result<Unit> {
val normalizedEmail = AuthEmailValidator.normalize(email)
AuthEmailValidator.validate(normalizedEmail, rejectDisposable = false)?.let { message ->
AuthEmailValidator.validate(normalizedEmail, rejectDisposable = false)?.let { messageRes ->
val message = context.getString(messageRes)
_authState.value = AuthState.Error(message)
return Result.failure(Exception(message))
}
Expand Down Expand Up @@ -575,13 +577,13 @@ class AuthRepository @Inject constructor(
AppLogger.breadcrumb("Auth", "email_sign_in_success")
Result.success(Unit)
} else {
val message = safeErrorMessage(null, "Sign in failed")
val message = safeErrorMessage(null, context.getString(R.string.auth_signin_failed))
_authState.value = AuthState.Error(message)
AppLogger.breadcrumb("Auth", "email_sign_in_no_session", severity = "warning")
Result.failure(Exception(message))
}
} catch (e: Exception) {
val message = safeErrorMessage(e, "Sign in failed")
val message = safeErrorMessage(e, context.getString(R.string.auth_signin_failed))
_authState.value = AuthState.Error(message)
AppLogger.breadcrumb("Auth", "email_sign_in_failed ${e::class.java.simpleName}", severity = "warning")
Result.failure(Exception(message))
Expand All @@ -593,7 +595,8 @@ class AuthRepository @Inject constructor(
*/
suspend fun signUp(email: String, password: String): Result<Unit> {
val normalizedEmail = AuthEmailValidator.normalize(email)
AuthEmailValidator.validate(normalizedEmail)?.let { message ->
AuthEmailValidator.validate(normalizedEmail)?.let { messageRes ->
val message = context.getString(messageRes)
_authState.value = AuthState.Error(message)
return Result.failure(Exception(message))
}
Expand All @@ -608,7 +611,7 @@ class AuthRepository @Inject constructor(
}
}
} catch (e: Exception) {
val message = safeErrorMessage(e, "Sign up failed")
val message = safeErrorMessage(e, context.getString(R.string.auth_signup_failed))
_authState.value = AuthState.Error(message)
AppLogger.recordException(
throwable = e,
Expand All @@ -632,7 +635,7 @@ class AuthRepository @Inject constructor(
url = Constants.AUTH_LOGIN_URL,
email = email,
password = password,
defaultError = "Sign in failed"
defaultError = context.getString(R.string.auth_signin_failed)
)
}

Expand All @@ -641,7 +644,7 @@ class AuthRepository @Inject constructor(
url = Constants.CLOUD_AUTH_EMAIL_URL,
email = email,
password = password,
defaultError = "Unable to create account"
defaultError = context.getString(R.string.auth_unable_create_account)
)
}

Expand Down Expand Up @@ -675,7 +678,7 @@ class AuthRepository @Inject constructor(
val accessToken = json?.optString("access_token").orEmpty()
val refreshToken = json?.optString("refresh_token").orEmpty()
if (accessToken.isBlank() || refreshToken.isBlank()) {
throw IllegalStateException("Auth response incomplete")
throw IllegalStateException(context.getString(R.string.auth_response_incomplete))
}
CloudAccountSession(accessToken, refreshToken)
}
Expand Down Expand Up @@ -739,12 +742,13 @@ class AuthRepository @Inject constructor(
AppLogger.breadcrumb("Auth", "session_import_success")
Result.success(Unit)
} else {
_authState.value = AuthState.Error("Failed to import auth session")
val message = context.getString(R.string.auth_failed_import_session)
_authState.value = AuthState.Error(message)
AppLogger.breadcrumb("Auth", "session_import_missing_user", severity = "warning")
Result.failure(Exception("Failed to import auth session"))
Result.failure(Exception(message))
}
} catch (e: Exception) {
val message = safeErrorMessage(e, "Sign in failed")
val message = safeErrorMessage(e, context.getString(R.string.auth_signin_failed))
_authState.value = AuthState.Error(message)
AppLogger.recordException(
throwable = e,
Expand Down Expand Up @@ -820,24 +824,27 @@ class AuthRepository @Inject constructor(
_authState.value = AuthState.Authenticated(user.id, user.email ?: "", profile)
Result.success(Unit)
} else {
_authState.value = AuthState.Error("Google Sign-In failed")
Result.failure(Exception("Google Sign-In failed"))
val message = context.getString(R.string.auth_google_failed)
_authState.value = AuthState.Error(message)
Result.failure(Exception(message))
}
} else {
_authState.value = AuthState.Error("Unexpected credential type")
Result.failure(Exception("Unexpected credential type"))
val message = context.getString(R.string.auth_unexpected_credential)
_authState.value = AuthState.Error(message)
Result.failure(Exception(message))
}
}
else -> {
_authState.value = AuthState.Error("Unexpected credential type")
Result.failure(Exception("Unexpected credential type"))
val message = context.getString(R.string.auth_unexpected_credential)
_authState.value = AuthState.Error(message)
Result.failure(Exception(message))
}
}
} catch (e: GoogleIdTokenParsingException) {
_authState.value = AuthState.Error("Failed to parse Google credentials")
_authState.value = AuthState.Error(context.getString(R.string.auth_failed_parse_google))
Result.failure(e)
} catch (e: Exception) {
_authState.value = AuthState.Error(e.message ?: "Google Sign-In failed")
_authState.value = AuthState.Error(e.message ?: context.getString(R.string.auth_google_failed))
Result.failure(e)
}
}
Expand Down Expand Up @@ -943,11 +950,11 @@ class AuthRepository @Inject constructor(
return when {
"arvio cloud moved" in message || "password setup" in message -> rawMessage
Constants.USE_NETLIFY_CLOUD_SYNC && "invalid email or password" in message -> netlifyPasswordHelp
"database error saving new user" in message -> "Account already exists. Sign in instead."
"settingssessionmanager" in message -> "Sign in failed. Please try again."
"invalid login credentials" in message -> "Invalid email or password."
"email not confirmed" in message || "confirm" in message -> "Please verify your email to continue."
"user already" in message || "already registered" in message -> "Account already exists. Sign in instead."
"database error saving new user" in message -> context.getString(R.string.auth_account_exists)
"settingssessionmanager" in message -> context.getString(R.string.auth_signin_retry)
"invalid login credentials" in message -> context.getString(R.string.auth_invalid_credentials)
"email not confirmed" in message || "confirm" in message -> context.getString(R.string.auth_verify_email)
"user already" in message || "already registered" in message -> context.getString(R.string.auth_account_exists)
else -> fallback
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.arflix.tv.data.repository

import android.content.Context
import com.arflix.tv.data.api.TraktApi
import com.arflix.tv.data.model.CatalogDiscoveryResult
import com.arflix.tv.data.model.CatalogSourceType
import com.arflix.tv.R
import com.arflix.tv.util.Constants
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
Expand All @@ -15,6 +18,7 @@ import javax.inject.Singleton

@Singleton
class CatalogDiscoveryRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val traktApi: TraktApi,
private val okHttpClient: OkHttpClient
) {
Expand Down Expand Up @@ -43,7 +47,7 @@ class CatalogDiscoveryRepository @Inject constructor(
Result.failure(
trakt.exceptionOrNull()
?: mdblist.exceptionOrNull()
?: IllegalStateException("Failed to search catalogs")
?: IllegalStateException(context.getString(R.string.catalog_failed_search))
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.arflix.tv.data.model.CatalogSourceType
import com.arflix.tv.data.model.CatalogValidationResult
import com.arflix.tv.data.model.Category
import com.arflix.tv.data.repository.HomeServerCatalogCandidate
import com.arflix.tv.R
import com.arflix.tv.util.CatalogUrlParser
import com.arflix.tv.util.Constants
import com.arflix.tv.util.ParsedCatalogUrl
Expand Down Expand Up @@ -709,11 +710,11 @@ class CatalogRepository @Inject constructor(
val sourceType = validation.sourceType
val resolved = resolveMetadata(normalizedUrl, sourceType)
?: fallbackMetadata(normalizedUrl, sourceType)
?: return Result.failure(IllegalArgumentException("Failed to read catalog metadata"))
?: return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_failed_read_metadata)))

val current = getCatalogs().toMutableList()
if (current.any { it.sourceUrl.equals(normalizedUrl, ignoreCase = true) }) {
return Result.failure(IllegalArgumentException("Catalog already added"))
return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_already_added)))
}

val newCatalog = CatalogConfig(
Expand All @@ -732,10 +733,10 @@ class CatalogRepository @Inject constructor(
suspend fun updateCustomCatalog(catalogId: String, rawUrl: String): Result<CatalogConfig> {
val current = getCatalogs().toMutableList()
val index = current.indexOfFirst { it.id == catalogId }
if (index < 0) return Result.failure(IllegalArgumentException("Catalog not found"))
if (index < 0) return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_not_found)))
val existing = current[index]
if (existing.isPreinstalled) {
return Result.failure(IllegalArgumentException("Preinstalled catalogs cannot be edited"))
return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_preinstalled_no_edit)))
}

val validation = validateCatalogUrl(rawUrl)
Expand All @@ -745,12 +746,12 @@ class CatalogRepository @Inject constructor(

val normalizedUrl = validation.normalizedUrl
if (current.any { it.id != catalogId && it.sourceUrl.equals(normalizedUrl, ignoreCase = true) }) {
return Result.failure(IllegalArgumentException("Catalog already added"))
return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_already_added)))
}

val resolved = resolveMetadata(normalizedUrl, validation.sourceType)
?: fallbackMetadata(normalizedUrl, validation.sourceType)
?: return Result.failure(IllegalArgumentException("Failed to read catalog metadata"))
?: return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_failed_read_metadata)))
val updated = existing.copy(
title = resolved.title,
sourceType = validation.sourceType,
Expand All @@ -765,7 +766,7 @@ class CatalogRepository @Inject constructor(
suspend fun removeCustomCatalog(catalogId: String): Result<Unit> {
val current = getCatalogs().toMutableList()
val target = current.firstOrNull { it.id == catalogId }
?: return Result.failure(IllegalArgumentException("Catalog not found"))
?: return Result.failure(IllegalArgumentException(context.getString(R.string.catalog_not_found)))
val profileId = activeProfileId()
if (isPreinstalledCatalog(target)) {
hidePreinstalledCatalog(profileId, catalogId)
Expand Down Expand Up @@ -910,7 +911,7 @@ class CatalogRepository @Inject constructor(
}
}
CatalogSourceType.MDBLIST -> ResolvedCatalog(
title = "MDBList Catalog",
title = context.getString(R.string.catalog_mdblist_title),
sourceRef = "mdblist:$url"
)
CatalogSourceType.PREINSTALLED -> null
Expand Down Expand Up @@ -979,9 +980,10 @@ class CatalogRepository @Inject constructor(
?.replace(" - MDBList", "", ignoreCase = true)

val titleFromSlug = extractMdblistSlugTitle(url)
val finalTitle = (titleFromMeta ?: titleFromTag ?: titleFromSlug ?: "MDBList Catalog").trim()
val mdblistFallbackTitle = context.getString(R.string.catalog_mdblist_title)
val finalTitle = (titleFromMeta ?: titleFromTag ?: titleFromSlug ?: mdblistFallbackTitle).trim()
return ResolvedCatalog(
title = finalTitle.ifBlank { "MDBList Catalog" },
title = finalTitle.ifBlank { mdblistFallbackTitle },
sourceRef = "mdblist:$url"
)
}
Expand Down
Loading