From d6a86a1a69e7dcd0fb2aa04b2964e664af6c2096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Sat, 31 Jan 2026 01:55:55 +0100 Subject: [PATCH] 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. --- app/proguard-rules.pro | 6 ++++++ .../java/com/fastmask/data/api/JmapApi.kt | 19 +++++++++++++++---- .../com/fastmask/ui/auth/LoginViewModel.kt | 3 ++- .../ui/detail/MaskedEmailDetailViewModel.kt | 3 +++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7e5ec41..9f3f11e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -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 diff --git a/app/src/main/java/com/fastmask/data/api/JmapApi.kt b/app/src/main/java/com/fastmask/data/api/JmapApi.kt index a593863..cb28e13 100644 --- a/app/src/main/java/com/fastmask/data/api/JmapApi.kt +++ b/app/src/main/java/com/fastmask/data/api/JmapApi.kt @@ -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> = 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 = 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 = 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 = 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) { diff --git a/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt b/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt index 000e6fa..fc9ba78 100644 --- a/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt +++ b/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt @@ -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 diff --git a/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt b/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt index 44b29a9..01393d2 100644 --- a/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt +++ b/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt @@ -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" ) }