Skip to content
Merged
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
11 changes: 11 additions & 0 deletions app/src/main/java/com/pinakes/app/data/network/ApiResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import kotlinx.serialization.json.Json
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException
import javax.net.ssl.SSLException

/**
* A normalized result for every API call. Maps the {data, meta, error} envelope and
Expand Down Expand Up @@ -40,6 +41,10 @@ object ErrorCodes {
const val VALIDATION = "validation" // 400 / 422
const val SERVER_ERROR = "server_error" // 5xx
const val NETWORK = "network" // no connectivity / timeout
// TLS/certificate failure (SSLException — a subtype of IOException). Kept distinct
// from NETWORK so the UI can say "the server's certificate isn't trusted" instead of
// sending the user to chase DNS/connectivity when a browser reaches the same host fine.
const val TLS = "tls"
const val UNKNOWN = "unknown"
}

Expand Down Expand Up @@ -73,6 +78,10 @@ suspend fun <T> apiCall(block: suspend () -> Envelope<T>): ApiResult<T> = try {
}
} catch (e: HttpException) {
e.toFailure()
} catch (e: SSLException) {
// SSLException extends IOException — catch it first so a TLS/certificate failure is
// reported as TLS (not the generic "check your connection" network message).
ApiResult.Failure(ErrorCodes.TLS, e.message ?: "TLS error", 0)
} catch (e: IOException) {
ApiResult.Failure(ErrorCodes.NETWORK, e.message ?: "Network error", 0)
} catch (e: Throwable) {
Expand All @@ -97,6 +106,8 @@ suspend fun <T> apiResponse(block: suspend () -> Response<Envelope<T>>): Pair<Ap
} else {
parseErrorBody(response.errorBody()?.string(), response.code()) to response
}
} catch (e: SSLException) {
ApiResult.Failure(ErrorCodes.TLS, e.message ?: "TLS error", 0) to null
} catch (e: IOException) {
ApiResult.Failure(ErrorCodes.NETWORK, e.message ?: "Network error", 0) to null
} catch (e: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.pinakes.app.data.network
import com.pinakes.app.data.model.BookClubEnvelope
import retrofit2.HttpException
import java.io.IOException
import javax.net.ssl.SSLException

/**
* Bridges the Book Club envelope (`{success, data, error}`) into the shared [ApiResult] the
Expand Down Expand Up @@ -34,6 +35,8 @@ suspend fun <T> bookClubCall(block: suspend () -> BookClubEnvelope<T>): ApiResul
}
} catch (e: HttpException) {
e.toFailure()
} catch (e: SSLException) {
ApiResult.Failure(ErrorCodes.TLS, e.message ?: "TLS error", 0)
} catch (e: IOException) {
ApiResult.Failure(ErrorCodes.NETWORK, e.message ?: "Network error", 0)
} catch (e: Throwable) {
Expand All @@ -53,6 +56,8 @@ suspend fun bookClubCallUnit(block: suspend () -> BookClubEnvelope<Unit>): ApiRe
}
} catch (e: HttpException) {
e.toFailure()
} catch (e: SSLException) {
ApiResult.Failure(ErrorCodes.TLS, e.message ?: "TLS error", 0)
} catch (e: IOException) {
ApiResult.Failure(ErrorCodes.NETWORK, e.message ?: "Network error", 0)
} catch (e: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class LoginViewModel @Inject constructor(
else state.copy(error = null, errorRes = R.string.login_error_rate_limited, errorArg = null)
}
ErrorCodes.NETWORK -> state.copy(error = null, errorRes = R.string.login_error_network, errorArg = null)
ErrorCodes.TLS -> state.copy(error = null, errorRes = R.string.login_error_tls, errorArg = null)
ErrorCodes.FORBIDDEN -> state.copy(error = null, errorRes = R.string.login_error_forbidden, errorArg = null)
else ->
if (failure.message.isNotBlank()) state.copy(error = failure.message, errorRes = null, errorArg = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.pinakes.app.ui.screens.onboarding
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pinakes.app.data.network.ApiResult
import com.pinakes.app.data.network.ErrorCodes
import com.pinakes.app.data.repository.AuthRepository
import com.pinakes.app.data.repository.HealthDiscovery
import com.pinakes.app.R
Expand Down Expand Up @@ -70,8 +71,9 @@ class OnboardingViewModel @Inject constructor(private val auth: AuthRepository)
}

private fun applyError(state: OnboardingUiState, failure: ApiResult.Failure): OnboardingUiState = when (failure.code) {
"network" -> state.copy(error = null, errorRes = R.string.onboarding_error_network)
"not_found" -> state.copy(error = null, errorRes = R.string.onboarding_error_not_found)
ErrorCodes.NETWORK -> state.copy(error = null, errorRes = R.string.onboarding_error_network)
ErrorCodes.TLS -> state.copy(error = null, errorRes = R.string.onboarding_error_tls)
ErrorCodes.NOT_FOUND -> state.copy(error = null, errorRes = R.string.onboarding_error_not_found)
else ->
if (failure.message.isNotBlank()) state.copy(error = failure.message, errorRes = null)
else state.copy(error = null, errorRes = R.string.onboarding_error_generic)
Expand Down
2 changes: 2 additions & 0 deletions i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,12 @@
"login_error_rate_limited_seconds": "Zu viele Versuche. Versuche es in %1$ds erneut.",
"login_error_rate_limited": "Zu viele Versuche. Bitte warte und versuche es erneut.",
"login_error_network": "Netzwerkfehler. Überprüfe deine Verbindung.",
"login_error_tls": "Sichere Verbindung (TLS) fehlgeschlagen — das Zertifikat des Servers ist nicht vertrauenswürdig.",
"login_error_forbidden": "Dieses Konto darf sich hier nicht anmelden.",
"login_error_generic": "Anmeldung fehlgeschlagen. Bitte versuche es erneut.",
"onboarding_error_empty": "Gib die Webadresse deiner Bibliothek ein.",
"onboarding_error_network": "Diese Adresse konnte nicht erreicht werden. Überprüfe URL und Verbindung.",
"onboarding_error_tls": "Es konnte keine sichere HTTPS-Verbindung hergestellt werden — das TLS-Zertifikat des Servers ist nicht vertrauenswürdig. Prüfe das Zertifikat auf deinem Server.",
"onboarding_error_not_found": "Das sieht nicht nach einer Pinakes-Bibliothek aus (keine API gefunden).",
"onboarding_error_generic": "Verbindung zu dieser Bibliothek nicht möglich.",
"availability_reserved": "Vorgemerkt",
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,12 @@
"login_error_rate_limited_seconds": "Too many attempts. Try again in %1$ds.",
"login_error_rate_limited": "Too many attempts. Please wait and try again.",
"login_error_network": "Network error. Check your connection.",
"login_error_tls": "Secure connection (TLS) failed — the server's certificate isn't trusted.",
"login_error_forbidden": "This account isn't allowed to sign in here.",
"login_error_generic": "Sign-in failed. Please try again.",
"onboarding_error_empty": "Enter your library's web address.",
"onboarding_error_network": "Couldn't reach that address. Check the URL and your connection.",
"onboarding_error_tls": "Couldn't establish a secure HTTPS connection — the server's TLS certificate isn't trusted. Check the certificate on your server.",
"onboarding_error_not_found": "This doesn't look like a Pinakes library (no API found there).",
"onboarding_error_generic": "Couldn't connect to that library.",
"availability_reserved": "Reserved",
Expand Down
2 changes: 2 additions & 0 deletions i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,12 @@
"login_error_rate_limited_seconds": "Trop de tentatives. Réessayez dans %1$ds.",
"login_error_rate_limited": "Trop de tentatives. Veuillez patienter et réessayer.",
"login_error_network": "Erreur réseau. Vérifiez votre connexion.",
"login_error_tls": "Échec de la connexion sécurisée (TLS) — le certificat du serveur n'est pas approuvé.",
"login_error_forbidden": "Ce compte n'est pas autorisé à se connecter ici.",
"login_error_generic": "Échec de la connexion. Veuillez réessayer.",
"onboarding_error_empty": "Saisissez l'adresse web de votre bibliothèque.",
"onboarding_error_network": "Impossible d'atteindre cette adresse. Vérifiez l'URL et votre connexion.",
"onboarding_error_tls": "Impossible d'établir une connexion HTTPS sécurisée — le certificat TLS du serveur n'est pas approuvé. Vérifiez le certificat sur votre serveur.",
"onboarding_error_not_found": "Cela ne ressemble pas à une bibliothèque Pinakes (aucune API trouvée).",
"onboarding_error_generic": "Impossible de se connecter à cette bibliothèque.",
"availability_reserved": "Réservé",
Expand Down
2 changes: 2 additions & 0 deletions i18n/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,12 @@
"login_error_rate_limited_seconds": "Troppi tentativi. Riprova tra %1$ds.",
"login_error_rate_limited": "Troppi tentativi. Attendi e riprova.",
"login_error_network": "Errore di rete. Controlla la connessione.",
"login_error_tls": "Connessione sicura (TLS) fallita — il certificato del server non è attendibile.",
"login_error_forbidden": "Questo account non può accedere qui.",
"login_error_generic": "Accesso non riuscito. Riprova.",
"onboarding_error_empty": "Inserisci l'indirizzo web della tua biblioteca.",
"onboarding_error_network": "Impossibile raggiungere quell'indirizzo. Controlla l'URL e la connessione.",
"onboarding_error_tls": "Impossibile stabilire una connessione HTTPS sicura — il certificato TLS del server non è attendibile. Controlla il certificato sul server.",
"onboarding_error_not_found": "Questa non sembra una biblioteca Pinakes (nessuna API trovata).",
"onboarding_error_generic": "Impossibile connettersi a quella biblioteca.",
"availability_reserved": "Prenotato",
Expand Down
Loading