Improve session handling and UI state updates

Refactored JmapApi to ensure session is initialized before API calls, reducing potential errors. Updated LoginViewModel to remove all whitespace from tokens before login. MaskedEmailDetailViewModel now consistently resets isUpdating flag on data load and error, improving UI state accuracy. Added ProGuard rules to suppress warnings for Google Error Prone annotations.
This commit is contained in:
Paweł Orzech 2026-01-31 01:55:55 +01:00
parent 5eee3a4fed
commit d6a86a1a69
No known key found for this signature in database
4 changed files with 26 additions and 5 deletions

View file

@ -37,3 +37,9 @@
# OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
# Google Tink / Error Prone annotations
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
-dontwarn com.google.errorprone.annotations.CheckReturnValue
-dontwarn com.google.errorprone.annotations.Immutable
-dontwarn com.google.errorprone.annotations.RestrictedApi

View file

@ -33,8 +33,14 @@ class JmapApi @Inject constructor(
fun getApiUrl(): String = cachedSession?.apiUrl ?: JmapService.FASTMAIL_API_URL
private suspend fun ensureSession(token: String): String {
cachedAccountId?.let { return it }
getSession(token).getOrThrow()
return cachedAccountId ?: throw IllegalStateException("Failed to get account ID from session")
}
suspend fun getMaskedEmails(token: String): Result<List<MaskedEmailDto>> = runCatching {
val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized")
val accountId = ensureSession(token)
val authHeader = "Bearer $token"
val methodCall = buildJsonArray {
@ -63,7 +69,7 @@ class JmapApi @Inject constructor(
token: String,
create: MaskedEmailCreate
): Result<MaskedEmailDto> = runCatching {
val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized")
val accountId = ensureSession(token)
val authHeader = "Bearer $token"
val createObject = buildJsonObject {
@ -104,7 +110,7 @@ class JmapApi @Inject constructor(
id: String,
update: MaskedEmailUpdate
): Result<Unit> = runCatching {
val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized")
val accountId = ensureSession(token)
val authHeader = "Bearer $token"
val updateObject = buildJsonObject {
@ -140,7 +146,7 @@ class JmapApi @Inject constructor(
}
suspend fun deleteMaskedEmail(token: String, id: String): Result<Unit> = runCatching {
val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized")
val accountId = ensureSession(token)
val authHeader = "Bearer $token"
val methodCall = buildJsonArray {
@ -227,6 +233,11 @@ class JmapApi @Inject constructor(
setResponse.notUpdated?.get(id)?.let { error ->
throw JmapException("Failed to update: ${error.type} - ${error.description}")
}
// Verify the update actually succeeded
if (setResponse.updated?.containsKey(id) != true) {
throw JmapException("Update not confirmed by server - ID not found in updated response")
}
}
private fun parseSetResponseDestroyed(response: JmapResponse, id: String) {

View file

@ -30,7 +30,8 @@ class LoginViewModel @Inject constructor(
}
fun login() {
val token = _uiState.value.token.trim()
// Remove all whitespace characters (spaces, newlines, tabs) from the token
val token = _uiState.value.token.filterNot { it.isWhitespace() }
if (token.isBlank()) {
_uiState.update { it.copy(error = "Please enter your API token") }
return

View file

@ -52,6 +52,7 @@ class MaskedEmailDetailViewModel @Inject constructor(
_uiState.update {
it.copy(
isLoading = false,
isUpdating = false,
email = email,
editedDescription = email.description ?: "",
editedForDomain = email.forDomain ?: "",
@ -62,6 +63,7 @@ class MaskedEmailDetailViewModel @Inject constructor(
_uiState.update {
it.copy(
isLoading = false,
isUpdating = false,
error = "Email not found"
)
}
@ -71,6 +73,7 @@ class MaskedEmailDetailViewModel @Inject constructor(
_uiState.update {
it.copy(
isLoading = false,
isUpdating = false,
error = error.message ?: "Failed to load email"
)
}