From d1fc477bcd215065b587893ccb7add04284c7a0b Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:55:37 +0300 Subject: [PATCH] fix: resolve all build compilation and style check warnings (#597) * fix: resolve all build compilation and style check warnings * fix: remove unused import in SystemViewModel --- .../remote/DashboardSessionTokenRefresher.kt | 7 +- .../hermescontrol/data/ws/HermesWsClient.kt | 2 +- .../ui/analytics/AnalyticsScreen.kt | 13 +- .../ui/analytics/AnalyticsViewModel.kt | 16 +- .../ui/authlogin/AuthLoginViewModel.kt | 4 +- .../ui/channels/ChannelsViewModel.kt | 16 +- .../m57/hermescontrol/ui/chat/ChatBubble.kt | 189 ++++++++++-------- .../m57/hermescontrol/ui/chat/ChatScreen.kt | 12 +- .../hermescontrol/ui/chat/ChatViewModel.kt | 17 +- .../m57/hermescontrol/ui/chat/MarkdownText.kt | 54 +++-- .../hermescontrol/ui/config/ConfigScreen.kt | 8 +- .../ui/cron/CronJobsViewModel.kt | 2 +- .../hermescontrol/ui/mcp/McpServersScreen.kt | 6 +- .../m57/hermescontrol/ui/model/ModelScreen.kt | 6 +- .../hermescontrol/ui/plugins/PluginsScreen.kt | 3 +- .../ui/process/ProcessesScreen.kt | 25 ++- .../ui/profiles/ProfilesViewModel.kt | 2 +- .../ui/providers/ProvidersScreen.kt | 12 +- .../ui/providers/ProvidersViewModel.kt | 21 +- .../ui/sessions/SessionsScreen.kt | 8 +- .../ui/sessions/SessionsViewModel.kt | 11 +- .../ui/skills/SkillsViewModel.kt | 6 +- .../hermescontrol/ui/system/SystemScreen.kt | 4 +- .../ui/system/SystemViewModel.kt | 10 +- .../data/local/AuthManagerTest.kt | 4 +- .../data/remote/HermesApiServiceTest.kt | 9 +- .../ui/chat/ChatViewModelTest.kt | 16 +- .../ui/chat/MarkdownTextFeatureTest.kt | 2 +- 28 files changed, 293 insertions(+), 192 deletions(-) diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/DashboardSessionTokenRefresher.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/DashboardSessionTokenRefresher.kt index ad450144..7c8f6b39 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/DashboardSessionTokenRefresher.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/DashboardSessionTokenRefresher.kt @@ -25,7 +25,12 @@ internal object DashboardSessionTokenRefresher { client: OkHttpClient, ): String? = try { - val request = Request.Builder().url(baseUrl).get().build() + val request = + Request + .Builder() + .url(baseUrl) + .get() + .build() client.newCall(request).execute().use { response -> if (!response.isSuccessful) return null // Cap the read — the SPA HTML can be large, and we only need the diff --git a/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt b/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt index 62b9ffd5..ebf55c74 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt @@ -276,7 +276,7 @@ object HermesWsClient { } if (response.isSuccessful) { - val body = response.body?.string() ?: "" + val body = response.body.string() val ticketMatch = Regex("""\"ticket\":\"([^\"]+)\"""").find(body) val ticket = ticketMatch?.groupValues?.getOrNull(1) if (!ticket.isNullOrBlank()) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt index 188b5167..d3435ade 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt @@ -73,26 +73,31 @@ fun AnalyticsScreen( modifier = modifier, ) { when { - state.isLoading && state.usage == null -> LoadingState() + state.isLoading && state.usage == null -> { + LoadingState() + } - state.errorMessage != null && state.usage == null -> + state.errorMessage != null && state.usage == null -> { ErrorState( message = state.errorMessage ?: stringResource(R.string.error_unknown), onRetry = { viewModel.load() }, ) + } - state.usage == null -> + state.usage == null -> { EmptyState( title = stringResource(R.string.analytics_empty_title), subtitle = stringResource(R.string.analytics_empty_desc), icon = Icons.Filled.BarChart, ) + } - else -> + else -> { AnalyticsContent( state = state, onDaysSelected = viewModel::setDays, ) + } } } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsViewModel.kt index 683b8f4e..84f0e942 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsViewModel.kt @@ -105,13 +105,21 @@ class AnalyticsViewModel : ViewModel() { } val error = when { - usage == null && models == null -> + usage == null && models == null -> { failure?.message ?: "Failed to load analytics" - usage == null -> + } + + usage == null -> { failure?.message ?: "Failed to load usage analytics" - models == null -> + } + + models == null -> { failure?.message ?: "Failed to load model analytics" - else -> null + } + + else -> { + null + } } // Only update UI if this is still the active day window. if (_uiState.value.days == days) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/authlogin/AuthLoginViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/authlogin/AuthLoginViewModel.kt index bf7c5b5f..af502dd1 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/authlogin/AuthLoginViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/authlogin/AuthLoginViewModel.kt @@ -228,7 +228,7 @@ class AuthLoginViewModel( .get() .build() val resp = probeClient.newCall(req).execute() - val body = resp.body?.string() ?: "" + val body = resp.body.string() // Extract __HERMES_SESSION_TOKEN__ from the SPA HTML val tokenMatch = Regex("""__HERMES_SESSION_TOKEN__\s*=\s*"([^"]+)"""").find(body) extractedToken = tokenMatch?.groupValues?.getOrNull(1) @@ -453,7 +453,7 @@ class AuthLoginViewModel( return null } - val ticketBody = ticketResp.body?.string() ?: "" + val ticketBody = ticketResp.body.string() val ticketMatch = Regex(""""ticket":"([^"]+)"""").find(ticketBody) val ticket = ticketMatch?.groupValues?.getOrNull(1) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt index 11a1ba12..a230d548 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsViewModel.kt @@ -72,9 +72,9 @@ class ChannelsViewModel : _uiState.update { it.copy( isLoading = false, - platforms = data?.platforms.orEmpty(), - gatewayStartCommand = data?.gatewayStartCommand ?: "hermes gateway start", - envPath = data?.envPath ?: "~/.hermes/.env", + platforms = data.platforms.orEmpty(), + gatewayStartCommand = data.gatewayStartCommand, + envPath = data.envPath, ) } }, @@ -218,7 +218,7 @@ class ChannelsViewModel : apiCall = { safeApiCall { ApiClient.hermesApi.testMessagingPlatform(platformId) } }, onStart = {}, onSuccess = { result -> - val msg = result?.message ?: "Test complete" + val msg = result.message _uiState.update { it.copy(testingId = null, toastMessage = msg) } @@ -297,8 +297,8 @@ class ChannelsViewModel : it.copy( onboardingPhase = OnboardingPhase.WAITING, onboardingSetup = setup, - onboardingQrDataUrl = setup?.qrPayload, - onboardingExpiresIn = formatExpiry(setup?.expiresAt), + onboardingQrDataUrl = setup.qrPayload, + onboardingExpiresIn = formatExpiry(setup.expiresAt), ) } @@ -330,7 +330,7 @@ class ChannelsViewModel : when (result) { is com.m57.hermescontrol.data.remote.NetworkResult.Success -> { val status = result.data - if (status?.status == "ready") { + if (status.status == "ready") { _uiState.update { it.copy( onboardingPhase = OnboardingPhase.READY, @@ -466,7 +466,7 @@ class ChannelsViewModel : ) } resetOnboarding() - if (applyResult?.needsRestart == true) { + if (applyResult.needsRestart) { restartGateway() } else { loadPlatforms() diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt index 430601c5..1b92976e 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt @@ -655,7 +655,7 @@ fun parseToolOutput( val truncated = if (raw.length > 100) raw.take(100) + "…" else raw if (truncated.isNotBlank()) "${config.summaryPrefix}$truncated" else null } else { - null ?: obj + obj .get("context") ?.takeIf { !it.isJsonNull } ?.asString @@ -764,7 +764,7 @@ fun parseToolOutput( val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = factSummaryText, @@ -900,7 +900,7 @@ fun parseToolOutput( val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = ssSummaryText, @@ -922,13 +922,24 @@ fun parseToolOutput( val mainOutput = when { errorMsg != null -> "❌ $errorMsg" + !success -> "❌ Failed" + usage != null -> "✅ $action in ${target ?: "memory"}\n $usage" - else -> "✅ ${target?.capitalize() ?: "Memory"} $action done" + + else -> "✅ ${target?.replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase( + java.util.Locale.getDefault(), + ) + } else { + it.toString() + } + } ?: "Memory"} $action done" } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -985,7 +996,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1047,7 +1058,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1081,7 +1092,7 @@ fun parseToolOutput( }?.joinToString("\n\n") ?: "📚 No skills found" val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1120,7 +1131,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1181,7 +1192,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1229,7 +1240,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1256,7 +1267,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1273,7 +1284,7 @@ fun parseToolOutput( val success = dataSource.get("success")?.takeIf { !it.isJsonNull }?.asBoolean ?: true val errorMsg = dataSource.get("error")?.takeIf { !it.isJsonNull }?.asString - val urlSummary = urls?.joinToString(", ") { it.asString?.take(40) ?: "?" } ?: "" + val urlSummary = urls?.joinToString(", ") { it.asString.take(40) } ?: "" val summaryText = "🕸️ ${urls?.size() ?: 0} page(s)" val mainOutput = when { @@ -1293,7 +1304,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1328,7 +1339,7 @@ fun parseToolOutput( }?.joinToString("\n\n") ?: "🔍 No matching tools" val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1372,7 +1383,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1410,7 +1421,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1443,7 +1454,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1488,7 +1499,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryText, @@ -1557,75 +1568,80 @@ fun parseToolOutput( buildString { if (errorMsg != null) { append("❌ $errorMsg") - } else if (multimodalText != null) { - append(multimodalText) - if (textSummary != null && multimodalText != textSummary) { - append("\n\n$textSummary") - } - } else if (summaryText != null) { - append(summaryText) - } else if (textSummary != null) { - append(textSummary) - } else if (appsArr != null) { - appsArr.forEachIndexed { idx, el -> - if (el.isJsonObject) { - val obj = el.asJsonObject - val name = - obj - .get("name") - ?.takeIf { !it.isJsonNull } - ?.asString ?: "?" - val pid = - obj - .get("pid") - ?.takeIf { !it.isJsonNull } - ?.asInt - append("${idx + 1}. $name") - if (pid != null) append(" (PID: $pid)") - append("\n") + } else { + val textPart = multimodalText ?: summaryText ?: textSummary + if (textPart != null) { + append(textPart) + if (textPart == multimodalText && textSummary != null && multimodalText != textSummary) { + append("\n\n$textSummary") } } - } else if (elementsArr != null) { - append("🖥️ ${elementsArr.size()} interactable elements") - if (mode != null) append(" (mode: $mode)") - if (width != null && height != null) append(" • ${width}x$height") - if (textSummary != null) append("\n\n$textSummary") - // Show first few elements as preview - val previewCount = minOf(elementsArr.size(), 5) - if (previewCount > 0) { - append("\n\n") - for (i in 0 until previewCount) { - val el = elementsArr[i] + + if (appsArr != null) { + if (isNotEmpty()) append("\n\n") + appsArr.forEachIndexed { idx, el -> if (el.isJsonObject) { val obj = el.asJsonObject - val idx = + val name = obj - .get("index") + .get("name") + ?.takeIf { !it.isJsonNull } + ?.asString ?: "?" + val pid = + obj + .get("pid") ?.takeIf { !it.isJsonNull } ?.asInt - val role = - obj - .get("role") - ?.takeIf { !it.isJsonNull } - ?.asString - val label = - obj - .get("label") - ?.takeIf { !it.isJsonNull } - ?.asString - append(" #$idx ${role ?: ""}${label?.let { " '$it'" } ?: ""}\n") + append("${idx + 1}. $name") + if (pid != null) append(" (PID: $pid)") + append("\n") } } - if (elementsArr.size() > previewCount) { - append(" ... and ${elementsArr.size() - previewCount} more") + } else if (elementsArr != null) { + if (isNotEmpty()) append("\n\n") + append("🖥️ ${elementsArr.size()} interactable elements") + if (mode != null) append(" (mode: $mode)") + if (width != null && height != null) append(" • ${width}x$height") + if (textSummary != null) append("\n\n$textSummary") + // Show first few elements as preview + val previewCount = minOf(elementsArr.size(), 5) + if (previewCount > 0) { + append("\n\n") + for (i in 0 until previewCount) { + val el = elementsArr[i] + if (el.isJsonObject) { + val obj = el.asJsonObject + val idx = + obj + .get("index") + ?.takeIf { !it.isJsonNull } + ?.asInt + val role = + obj + .get("role") + ?.takeIf { !it.isJsonNull } + ?.asString + val label = + obj + .get("label") + ?.takeIf { !it.isJsonNull } + ?.asString + append(" #$idx ${role ?: ""}${label?.let { " '$it'" } ?: ""}\n") + } + } + if (elementsArr.size() > previewCount) { + append(" ... and ${elementsArr.size() - previewCount} more") + } } + } else if (action != null && + (multimodalText == null && summaryText == null && textSummary == null) + ) { + append("$action:") + if (ok != null) append(" ${if (ok) "✅ ok" else "❌ failed"}") + if (message != null) append("\n$message") + } else if (isEmpty()) { + append("🖥️ Computer use action") } - } else if (action != null) { - append("$action:") - if (ok != null) append(" ${if (ok) "✅ ok" else "❌ failed"}") - if (message != null) append("\n$message") - } else { - append("🖥️ Computer use action") } if (visionAnalysis != null) { append("\n\n━━━ Vision Analysis ━━━\n$visionAnalysis") @@ -1633,7 +1649,7 @@ fun parseToolOutput( } val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble return ParsedToolData( - toolName = resolvedToolName ?: "", + toolName = resolvedToolName, args = args, result = dataSource.entrySet().associate { it.key to it.value.toString() }, summaryText = summaryLine, @@ -1657,8 +1673,16 @@ fun parseToolOutput( ( dataSource.get("output")?.takeIf { !it.isJsonNull }?.asString ?: listOfNotNull( - dataSource.get("stdout")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() }, - dataSource.get("stderr")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() }, + dataSource + .get("stdout") + ?.takeIf { !it.isJsonNull } + ?.asString + ?.takeIf { it.isNotEmpty() }, + dataSource + .get("stderr") + ?.takeIf { !it.isJsonNull } + ?.asString + ?.takeIf { it.isNotEmpty() }, ).joinToString("\n").takeIf { it.isNotEmpty() } )?.takeIf { it.isNotEmpty() } val exitCode = (dataSource.get("exit_code") ?: dataSource.get("exitCode"))?.toIntOrNull() @@ -2331,6 +2355,9 @@ private fun JsonElement?.toIntOrNull(): Int? { is JsonPrimitive -> { content.toIntOrNull() ?: content.toDoubleOrNull()?.toInt() } - else -> null + + else -> { + null + } } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt index 0c5a89fb..b04d1d8b 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt @@ -1197,7 +1197,7 @@ private fun SudoPromptDialog( modifier = Modifier.fillMaxWidth().testTag("sudo_password_input"), singleLine = true, visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Password), + keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password), ) } }, @@ -1247,7 +1247,7 @@ private fun SecretPromptDialog( modifier = Modifier.fillMaxWidth().testTag("secret_value_input"), singleLine = true, visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Password), + keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password), ) } }, @@ -2156,9 +2156,13 @@ private fun ReactionHeartsOverlay( val emojis = remember(reactionKind) { when (reactionKind) { - "ily", "<3", "good bot" -> + "ily", "<3", "good bot" -> { listOf("💗", "❤️", "💕", "💖", "🩷", "💘", "💝") - else -> listOf("💗", "❤️", "💕", "💖") + } + + else -> { + listOf("💗", "❤️", "💕", "💖") + } } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt index 4a0be060..bd1fb1f1 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt @@ -649,7 +649,7 @@ class ChatViewModel( _uiState.update { it.copy( isLoading = false, - errorMessage = "Error${if (method != null) " ($method)" else ""}: $errorMsg", + errorMessage = "Error ($method): $errorMsg", ) } } @@ -1084,8 +1084,10 @@ class ChatViewModel( val body = trimmed.removePrefix("/").trimStart() // Must be exactly "model" with no argument (or just whitespace). return body.equals("model", ignoreCase = true) || - body.startsWith("model ", ignoreCase = true) && - body.substringAfter("model").trim().isEmpty() + ( + body.startsWith("model ", ignoreCase = true) && + body.substringAfter("model").trim().isEmpty() + ) } /** Preload model options so the picker opens instantly (no spinner on /model). */ @@ -1343,7 +1345,7 @@ class ChatViewModel( } } - is NetworkResult.Failure -> Unit + is NetworkResult.Failure -> {} } } finally { isSyncingMessages = false @@ -1352,7 +1354,10 @@ class ChatViewModel( } private suspend fun fetchServerMessageCount(sessionId: String): Int { - val known = _uiState.value.sessions.find { it.id == sessionId }?.messageCount + val known = + _uiState.value.sessions + .find { it.id == sessionId } + ?.messageCount if (known != null) return known val result = withContext(Dispatchers.IO) { @@ -1762,7 +1767,7 @@ class ChatViewModel( return@launch } - val body = ticketResp.body?.string().orEmpty() + val body = ticketResp.body.string() val ticket = JSONObject(body).optString("ticket").takeIf { it.isNotBlank() } if (ticket.isNullOrBlank()) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt index fc8bb53f..f89e3d6d 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt @@ -104,13 +104,16 @@ fun MarkdownText( Column(modifier = modifier.fillMaxWidth()) { for (block in blocks) { when (block) { - is MdBlock.Code -> CodeBlockCard(code = block.code, textColor = textColor) + is MdBlock.Code -> { + CodeBlockCard(code = block.code, textColor = textColor) + } - is MdBlock.Hr -> + is MdBlock.Hr -> { HorizontalDivider( modifier = Modifier.padding(vertical = 8.dp), color = textColor.copy(alpha = 0.25f), ) + } is MdBlock.Heading -> { val fontSize = @@ -291,7 +294,9 @@ fun MarkdownText( } } - is MdBlock.Table -> MarkdownTable(block = block, textColor = textColor) + is MdBlock.Table -> { + MarkdownTable(block = block, textColor = textColor) + } is MdBlock.Footnotes -> { HorizontalDivider( @@ -510,7 +515,9 @@ internal fun parseBlocks(src: String): List { } } - line.isBlank() -> i++ + line.isBlank() -> { + i++ + } isHorizontalRule(line) -> { blocks.add(MdBlock.Hr) @@ -944,19 +951,36 @@ internal fun parseInline( } internal sealed interface MdBlock { - data class Code(val code: String) : MdBlock + data class Code( + val code: String, + ) : MdBlock - data class Heading(val level: Int, val text: String) : MdBlock + data class Heading( + val level: Int, + val text: String, + ) : MdBlock - data class Bullet(val text: String) : MdBlock + data class Bullet( + val text: String, + ) : MdBlock - data class Task(val checked: Boolean, val text: String) : MdBlock + data class Task( + val checked: Boolean, + val text: String, + ) : MdBlock - data class Ordered(val index: Int, val text: String) : MdBlock + data class Ordered( + val index: Int, + val text: String, + ) : MdBlock - data class Quote(val text: String) : MdBlock + data class Quote( + val text: String, + ) : MdBlock - data class Paragraph(val text: String) : MdBlock + data class Paragraph( + val text: String, + ) : MdBlock data class Table( val header: List, @@ -966,9 +990,13 @@ internal sealed interface MdBlock { object Hr : MdBlock - data class DefList(val items: List) : MdBlock + data class DefList( + val items: List, + ) : MdBlock - data class Footnotes(val notes: List) : MdBlock + data class Footnotes( + val notes: List, + ) : MdBlock } internal data class DefItem( diff --git a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt index 94c7e60b..010913d7 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon @@ -32,7 +33,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.SecondaryScrollableTabRow import androidx.compose.material3.Switch import androidx.compose.material3.Tab import androidx.compose.material3.Text @@ -494,7 +495,7 @@ private fun ConfigTabs( selectedCategory: String, onCategorySelected: (String) -> Unit, ) { - ScrollableTabRow( + SecondaryScrollableTabRow( selectedTabIndex = categories.indexOf(selectedCategory).coerceAtLeast(0), edgePadding = 0.dp, ) { @@ -637,7 +638,6 @@ private fun ConfigField( is JsonPrimitive -> it.content is JsonObject -> it.toString() is JsonArray -> it.toString() - else -> "" } } ?: "" @@ -676,7 +676,7 @@ private fun DropdownField( modifier = Modifier .fillMaxWidth() - .menuAnchor(), + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable), label = { Text(label) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, singleLine = true, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt index be2449a9..1e93c659 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt @@ -190,7 +190,7 @@ class CronJobsViewModel : } when (result) { is NetworkResult.Success -> { - val job = result.data ?: return@launch + val job = result.data _uiState.update { it.copy( editorState = diff --git a/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt index 93c00114..b7a63b44 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt @@ -34,9 +34,9 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.SecondaryTabRow import androidx.compose.material3.Switch import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -223,7 +223,7 @@ private fun AddServerSection( colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer), ) { Column(modifier = Modifier.padding(spacing.md)) { - TabRow(selectedTabIndex = if (state.addMode == AddServerMode.HTTP) 0 else 1) { + SecondaryTabRow(selectedTabIndex = if (state.addMode == AddServerMode.HTTP) 0 else 1) { Tab( selected = state.addMode == AddServerMode.HTTP, onClick = { viewModel.setAddMode(AddServerMode.HTTP) }, @@ -562,7 +562,7 @@ private fun CatalogSection( state.catalogError != null -> { ErrorState( - message = state.catalogError ?: "", + message = state.catalogError, onRetry = { viewModel.loadCatalog() }, ) } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt index 99242e28..99a63971 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt @@ -500,7 +500,7 @@ private fun AuxTasksDialog( if (isAuto) { "auto (use main model)" } else { - "${cur?.provider} · ${cur?.model ?: "(provider default)"}" + "${cur.provider} · ${cur.model.ifBlank { "(provider default)" }}" }, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -601,7 +601,7 @@ private fun PinnedSectionCard( pinnedModels.forEach { pinned -> val isActive = activeProfile?.provider == pinned.providerSlug && - activeProfile?.model == pinned.modelName + activeProfile.model == pinned.modelName Card( onClick = { onModelClick(pinned.providerSlug, pinned.modelName) }, @@ -778,7 +778,7 @@ private fun ProviderCard( filteredModels.forEach { model -> val isActive = activeProfile?.provider == provider.slug && - activeProfile?.model == model + activeProfile.model == model Card( onClick = { onModelClick(provider.slug, model) }, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), 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 3173d507..afe774df 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 @@ -26,6 +26,7 @@ import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon @@ -322,7 +323,7 @@ private fun PluginProviderDropdown( modifier = Modifier .fillMaxWidth() - .menuAnchor(), + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable), label = { Text(label) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, singleLine = true, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt index a857b2bd..6fa9ff66 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt @@ -76,30 +76,34 @@ fun ProcessesScreen( modifier = modifier, ) { when { - state.sessionId == null -> + state.sessionId == null -> { EmptyState( title = stringResource(R.string.processes_no_session_title), subtitle = stringResource(R.string.processes_no_session_desc), icon = Icons.Filled.Memory, ) + } - state.isLoading && state.processes.isEmpty() -> + state.isLoading && state.processes.isEmpty() -> { LoadingState() + } - state.errorMessage != null -> + state.errorMessage != null -> { ErrorState( message = state.errorMessage ?: stringResource(R.string.error_unknown), onRetry = { viewModel.load() }, ) + } - state.processes.isEmpty() -> + state.processes.isEmpty() -> { EmptyState( title = stringResource(R.string.processes_empty_title), subtitle = stringResource(R.string.processes_empty_desc), icon = Icons.Filled.Memory, ) + } - else -> + else -> { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = listContentPadding, @@ -113,6 +117,7 @@ fun ProcessesScreen( ) } } + } } } @@ -238,8 +243,14 @@ private fun formatUptime(seconds: Int): String { val m = seconds / 60 val s = seconds % 60 return when { - m <= 0 -> "${s}s" - m < 60 -> "${m}m ${s}s" + m <= 0 -> { + "${s}s" + } + + m < 60 -> { + "${m}m ${s}s" + } + else -> { val h = m / 60 "${h}h ${m % 60}m" diff --git a/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesViewModel.kt index 475fd571..3722f459 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesViewModel.kt @@ -332,7 +332,7 @@ class ProfilesViewModel : _uiState.update { it.copy( isSearchingHub = false, - hubSearchResults = result.data?.results.orEmpty(), + hubSearchResults = result.data.results.orEmpty(), ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt index a459536f..c6cb9918 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt @@ -274,21 +274,25 @@ private fun OAuthFlowDialog( Text(stringResource(R.string.providers_action_submit_code)) } } + OAuthFlowPhase.DONE -> { Button(onClick = onDismissFlow) { Text(stringResource(R.string.action_done)) } } + OAuthFlowPhase.ERROR -> { Button(onClick = onDismissFlow) { Text(stringResource(R.string.action_close)) } } + OAuthFlowPhase.SHOW_CLI -> { Button(onClick = onDismissFlow) { Text(stringResource(R.string.action_close)) } } + else -> { TextButton(onClick = onCancel) { Text(stringResource(R.string.action_cancel)) @@ -301,8 +305,6 @@ private fun OAuthFlowDialog( TextButton(onClick = onCancel) { Text(stringResource(R.string.action_cancel)) } - } else { - Unit } }, title = { Text(provider.name, style = MaterialTheme.typography.titleLarge) }, @@ -312,6 +314,7 @@ private fun OAuthFlowDialog( provider.flow == "external" -> { ExternalFlowContent(provider = provider) } + state.flowPhase == OAuthFlowPhase.WAITING_CODE && start?.authUrl != null -> { PkceFlowContent( authUrl = start.authUrl, @@ -321,6 +324,7 @@ private fun OAuthFlowDialog( error = state.flowErrorMessage, ) } + state.flowPhase == OAuthFlowPhase.POLLING -> { DeviceCodeFlowContent( start = start, @@ -329,15 +333,17 @@ private fun OAuthFlowDialog( error = state.flowErrorMessage, ) } + state.flowPhase == OAuthFlowPhase.DONE -> { Text( text = stringResource(R.string.providers_flow_success), style = MaterialTheme.typography.bodyMedium, ) } + state.flowErrorMessage != null -> { Text( - text = state.flowErrorMessage!!, + text = state.flowErrorMessage, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyMedium, ) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersViewModel.kt index 614d04b2..98613200 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersViewModel.kt @@ -77,7 +77,7 @@ class ProvidersViewModel : _uiState.update { it.copy( isLoading = false, - providers = data?.providers.orEmpty(), + providers = data.providers.orEmpty(), ) } }, @@ -161,16 +161,7 @@ class ProvidersViewModel : val result = safeApiCall { ApiClient.hermesApi.startOAuthLogin(provider.id) } when (result) { is NetworkResult.Success -> { - val start = - result.data ?: run { - _uiState.update { - it.copy( - flowPhase = OAuthFlowPhase.IDLE, - flowErrorMessage = "Empty start response", - ) - } - return@launch - } + val start = result.data _uiState.update { it.copy( flowPhase = @@ -224,13 +215,13 @@ class ProvidersViewModel : when (result) { is NetworkResult.Success -> { val submit = result.data - if (submit?.ok == true) { + if (submit.ok) { finishFlow("approved") } else { _uiState.update { it.copy( - flowStatus = submit?.status ?: "error", - flowErrorMessage = submit?.message ?: "Submission rejected", + flowStatus = submit.status ?: "error", + flowErrorMessage = submit.message ?: "Submission rejected", ) } } @@ -271,6 +262,7 @@ class ProvidersViewModel : finishFlow("approved") return@launch } + "denied", "expired", "error" -> { _uiState.update { it.copy( @@ -283,6 +275,7 @@ class ProvidersViewModel : } return@launch } + else -> { /* still pending — keep polling */ } } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt index dfa3e96c..d672d668 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Code @@ -37,7 +38,6 @@ import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.SelectAll -import androidx.compose.material.icons.filled.Send import androidx.compose.material.icons.filled.Terminal import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button @@ -115,7 +115,7 @@ import kotlin.math.abs */ private fun sourceIcon(source: String?): ImageVector? = when (source?.lowercase()) { - "telegram", "tg" -> Icons.Filled.Send + "telegram", "tg" -> Icons.AutoMirrored.Filled.Send "web", "dashboard" -> Icons.Filled.Language "api", "rest" -> Icons.Filled.Code "cli", "terminal" -> Icons.Filled.Terminal @@ -254,10 +254,6 @@ private fun JsonElement.searchText(): String? = .firstNotNullOfOrNull { fieldName -> this[fieldName]?.searchText() } ?: wrapperFieldNames.firstNotNullOfOrNull { fieldName -> this[fieldName]?.searchText() } } - - else -> { - null - } } private fun displayedSessions(state: SessionsUiState): List = diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt index 06b96db0..25316a56 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt @@ -85,8 +85,8 @@ class SessionsViewModel : ViewModel() { it.copy( isLoading = false, isLoadingMore = false, - sessions = data?.sessions.orEmpty(), - total = data?.total ?: 0, + sessions = data.sessions.orEmpty(), + total = data.total, selectedIds = emptySet(), ) } @@ -173,7 +173,7 @@ class SessionsViewModel : ViewModel() { _uiState.update { it.copy( isSearching = false, - searchResults = result.data?.results.orEmpty(), + searchResults = result.data.results.orEmpty(), searchError = null, ) } @@ -207,8 +207,7 @@ class SessionsViewModel : ViewModel() { it.copy( isLoadingStats = false, stats = - data?.let { SessionStats(total = it.total, active = it.active) } - ?: SessionStats(), + SessionStats(total = data.total, active = data.active), ) } }, @@ -318,7 +317,7 @@ class SessionsViewModel : ViewModel() { } when (result) { is NetworkResult.Success -> { - val promptText = result.data?.prompt ?: "No prompt available" + val promptText = result.data.prompt ?: "No prompt available" _uiState.update { it.copy(toastMessage = promptText) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt index ed295633..410d8083 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsViewModel.kt @@ -138,7 +138,7 @@ class SkillsViewModel( }, onStart = { _uiState.update { it.copy(isHubSearching = true) } }, onSuccess = { data -> - _uiState.update { it.copy(isHubSearching = false, hubResults = data?.results.orEmpty()) } + _uiState.update { it.copy(isHubSearching = false, hubResults = data.results.orEmpty()) } }, onError = { errorMsg -> _uiState.update { @@ -313,7 +313,7 @@ class SkillsViewModel( _uiState.update { it.copy( isLoadingPreview = false, - previewSkillContent = result.data?.content, + previewSkillContent = result.data.content, ) } } @@ -405,7 +405,7 @@ class SkillsViewModel( _uiState.update { it.copy( isLoadingContent = false, - skillContent = result.data?.content.orEmpty(), + skillContent = result.data.content.orEmpty(), ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt index e0f3bd64..01ad7667 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt @@ -1299,6 +1299,7 @@ private fun LazyListScope.operationsSection( // Debug share results state.debugShare?.let { share -> Spacer(modifier = Modifier.height(spacing.sm)) + @Suppress("DEPRECATION") val clipboardManager = LocalClipboardManager.current Row( @@ -1637,7 +1638,8 @@ private fun LazyListScope.actionLogSection( } private val actionButtonPadding = - androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp, vertical = 8.dp) + androidx.compose.foundation.layout + .PaddingValues(horizontal = 8.dp, vertical = 8.dp) @Composable private fun ActionButtonContent( diff --git a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt index 96b92ac6..ae0523e3 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemViewModel.kt @@ -8,7 +8,6 @@ import com.m57.hermescontrol.data.model.ActionResponse import com.m57.hermescontrol.data.model.ActionStatusResponse import com.m57.hermescontrol.data.model.CheckpointsResponse import com.m57.hermescontrol.data.model.CredentialPoolProvider -import com.m57.hermescontrol.data.model.CredentialPoolResponse import com.m57.hermescontrol.data.model.CuratorResponse import com.m57.hermescontrol.data.model.DebugShareResponse import com.m57.hermescontrol.data.model.DoctorResponse @@ -128,10 +127,7 @@ class SystemViewModel : curator = (curatorResult as? NetworkResult.Success)?.data, memory = (memoryResult as? NetworkResult.Success)?.data, credentials = - ( - (credResult as? NetworkResult.Success)?.data - as? CredentialPoolResponse - )?.providers ?: emptyList(), + ((credResult as? NetworkResult.Success)?.data)?.providers ?: emptyList(), checkpoints = (checkpointsResult as? NetworkResult.Success)?.data, hooks = (hooksResult as? NetworkResult.Success)?.data, updateInfo = (updateResult as? NetworkResult.Success)?.data, @@ -414,7 +410,7 @@ class SystemViewModel : _uiState.update { it.copy( toastMessage = "Backup triggered", - backupArchive = result.data?.archive, + backupArchive = result.data.archive, ) } loadAll() @@ -641,7 +637,7 @@ class SystemViewModel : val result = withContext(Dispatchers.IO) { apiCall() } when (result) { is NetworkResult.Success -> { - result.data?.name?.let { pollActionStatus(it) } + result.data.name?.let { pollActionStatus(it) } _uiState.update { it.copy(toastMessage = "$label started") } } diff --git a/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt b/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt index 3712d1dd..4d119e65 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/local/AuthManagerTest.kt @@ -52,7 +52,7 @@ class AuthManagerTest { every { android.util.Log.i(any(), any()) } returns 0 // Mock filesDir to point to temporary directory for DataStore - val tempDir = java.io.File(System.getProperty("java.io.tmpdir")) + val tempDir = java.io.File(System.getProperty("java.io.tmpdir") ?: "/tmp") testContext = TestContext(tempDir, mockContext) val tempFile = java.io.File(tempDir, "server_store.json") @@ -101,7 +101,7 @@ class AuthManagerTest { @After fun tearDown() { - val tempDir = java.io.File(System.getProperty("java.io.tmpdir")) + val tempDir = java.io.File(System.getProperty("java.io.tmpdir") ?: "/tmp") val tempFile = java.io.File(tempDir, "server_store.json") if (tempFile.exists()) { tempFile.delete() diff --git a/app/src/test/java/com/m57/hermescontrol/data/remote/HermesApiServiceTest.kt b/app/src/test/java/com/m57/hermescontrol/data/remote/HermesApiServiceTest.kt index 9d7c044c..a037dd9b 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/remote/HermesApiServiceTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/remote/HermesApiServiceTest.kt @@ -430,7 +430,14 @@ class HermesApiServiceTest { assertEquals(100L, body?.daily?.first()?.input_tokens) assertEquals(0.03, body?.totals?.total_estimated_cost) assertEquals(2, body?.skills?.summary?.distinct_skills_used) - assertEquals("x", body?.skills?.top_skills?.first()?.skill) + assertEquals( + "x", + body + ?.skills + ?.top_skills + ?.first() + ?.skill, + ) assertEquals(1, body?.tools?.size) assertEquals("terminal", body?.tools?.first()?.tool) assertEquals(21820, body?.tools?.first()?.count) diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt index 1ac57b6d..5a21598f 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt @@ -361,7 +361,8 @@ class ChatViewModelTest { ) assertTrue( "picker should have preloaded providers (cached at GatewayReady)", - viewModel.uiState.value.modelPickerProviders.isNotEmpty(), + viewModel.uiState.value.modelPickerProviders + .isNotEmpty(), ) verify(exactly = 0) { HermesWsClient.send(WsMethods.COMMAND_DISPATCH, any(), any()) } } @@ -930,7 +931,11 @@ class ChatViewModelTest { ), ) advanceUntilIdle() - assertEquals("clarify-789", viewModel.uiState.value.clarifyRequest?.clarifyId) + assertEquals( + "clarify-789", + viewModel.uiState.value.clarifyRequest + ?.clarifyId, + ) viewModel.dismissClarify() advanceUntilIdle() @@ -1593,11 +1598,14 @@ class ChatViewModelTest { advanceUntilIdle() assertTrue( "pendingAttachments must contain the image before send", - viewModel.uiState.value.pendingAttachments.isNotEmpty(), + viewModel.uiState.value.pendingAttachments + .isNotEmpty(), ) assertTrue( "attached png must have isImage=true", - viewModel.uiState.value.pendingAttachments.first().isImage, + viewModel.uiState.value.pendingAttachments + .first() + .isImage, ) viewModel.sendMessage("Here is an image") diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt index bfa0ca75..14debb08 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt @@ -158,7 +158,7 @@ class MarkdownTextFeatureTest { fun testHighlight_parses() { val an = parseInline("==mark==", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertEquals("mark", an.toString()) - assertTrue(an.spanStyles.any { it.item.background != null }) + assertTrue(an.spanStyles.any { it.item.background != Color.Unspecified }) } // 10. SUPERSCRIPT / SUBSCRIPT