diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/NetworkResult.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/NetworkResult.kt index e1ebad9c..494c1b28 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/NetworkResult.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/NetworkResult.kt @@ -1,5 +1,6 @@ package com.m57.hermescontrol.data.remote +import com.google.gson.JsonParser import kotlinx.coroutines.delay import retrofit2.Response import java.io.IOException @@ -37,12 +38,28 @@ sealed interface NetworkError { ) : NetworkError } -fun mapHttpError(code: Int): NetworkError { +fun mapHttpError( + code: Int, + errorBody: String? = null, +): NetworkError { if (code == 401) { return NetworkError.AuthExpired() } + // Try to parse FastAPI-style error detail from response body + val detail = + if (errorBody != null) { + try { + val parsed = com.google.gson.JsonParser.parseString(errorBody).asJsonObject + parsed.get("detail")?.asString + } catch (_: Exception) { + null + } + } else { + null + } + val message = - when (code) { + detail ?: when (code) { 400 -> "Bad Request (HTTP 400): The server could not understand the request." 403 -> "Forbidden (HTTP 403): You do not have permission to access this resource." 404 -> "Not Found (HTTP 404): The requested resource could not be found." @@ -88,12 +105,19 @@ suspend inline fun safeApiCall( } } else { val code = response.code() + // Read error body for detail message + val errorBody = + try { + response.errorBody()?.string() + } catch (_: Exception) { + null + } if ((code in 500..599 || code == 429) && attempt < retries) { val backoff = jitteredBackoff(500L * (1 shl attempt)) delay(backoff) continue } - return NetworkResult.Failure(mapHttpError(code)) + return NetworkResult.Failure(mapHttpError(code, errorBody)) } } catch (e: IOException) { lastException = e diff --git a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt index 28e6c962..9721ab17 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api @@ -159,18 +160,20 @@ fun PluginsScreen( OutlinedButton(onClick = { viewModel.updatePlugin(plugin.name) }) { Text(stringResource(R.string.plugins_action_update)) } - Spacer(modifier = Modifier.width(8.dp)) - OutlinedButton( - onClick = { viewModel.uninstallPlugin(plugin.name) }, - colors = - androidx.compose.material3.ButtonDefaults.outlinedButtonColors( - contentColor = MaterialTheme.colorScheme.error, - ), - ) { - Text(stringResource(R.string.plugins_action_uninstall)) + if (plugin.canRemove) { + Spacer(modifier = Modifier.width(8.dp)) + OutlinedButton( + onClick = { viewModel.uninstallPlugin(plugin.name) }, + colors = + ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error, + ), + ) { + Text(stringResource(R.string.plugins_action_uninstall)) + } } } else { - Button(onClick = { viewModel.installPlugin(plugin.name) }) { + Button(onClick = { viewModel.activatePlugin(plugin) }) { Text(stringResource(R.string.plugins_action_install)) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsViewModel.kt index 1789eacd..3c71265e 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsViewModel.kt @@ -79,25 +79,20 @@ class PluginsViewModel : ViewModel(), ToastHost { } } - fun installPlugin(name: String) { + fun activatePlugin(plugin: PluginInfo) { viewModelScope.launch { val result = withContext(Dispatchers.IO) { - safeApiCall { - ApiClient.hermesApi.installPlugin( - com.m57.hermescontrol.data.model - .AgentPluginInstallBody(name), - ) - } + safeApiCall { ApiClient.hermesApi.enablePlugin(plugin.name) } } when (result) { is NetworkResult.Success -> { - _uiState.update { it.copy(toastMessage = "Plugin installed successfully") } + _uiState.update { it.copy(toastMessage = "Plugin enabled successfully") } loadPlugins() } is NetworkResult.Failure -> { - _uiState.update { it.copy(toastMessage = "Failed to install plugin: ${result.error.message}") } + _uiState.update { it.copy(toastMessage = "Failed to enable plugin: ${result.error.message}") } } } }