diff --git a/app/src/main/java/com/m57/hermescontrol/data/remote/ApiClient.kt b/app/src/main/java/com/m57/hermescontrol/data/remote/ApiClient.kt index 19ea096a..cfde7772 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/remote/ApiClient.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/remote/ApiClient.kt @@ -24,6 +24,8 @@ object ApiClient { @Volatile private var service: HermesApiService? = null + private val connectionPool = okhttp3.ConnectionPool() + /** The current [HermesApiService] instance. Lazily created on first access. */ val hermesApi: HermesApiService get() { @@ -81,6 +83,7 @@ object ApiClient { val okHttp = OkHttpClient .Builder() + .connectionPool(connectionPool) .addInterceptor(authInterceptor) .addInterceptor(logging) .certificatePinner(certificatePinner) 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 6cc70260..3e2f9082 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 @@ -173,17 +173,18 @@ private fun UserBubble( ) val primary = MaterialTheme.colorScheme.primary val secondary = MaterialTheme.colorScheme.secondary + val onPrimary = MaterialTheme.colorScheme.onPrimary val avgLuminance = (primary.luminance() + secondary.luminance()) / 2f val userBubbleTextColor = if (avgLuminance > 0.5f) { - if (MaterialTheme.colorScheme.onPrimary.luminance() < 0.5f) { - MaterialTheme.colorScheme.onPrimary + if (onPrimary.luminance() < 0.5f) { + onPrimary } else { Color(0xFF1A1A24) } } else { - if (MaterialTheme.colorScheme.onPrimary.luminance() > 0.5f) { - MaterialTheme.colorScheme.onPrimary + if (onPrimary.luminance() > 0.5f) { + onPrimary } else { Color.White } @@ -279,16 +280,17 @@ private fun AssistantBubble( modifier: Modifier = Modifier, ) { val bubbleColor = if (isDarkTheme) AssistantBubble else AssistantBubbleLight + val onSurface = MaterialTheme.colorScheme.onSurface val textColor = if (bubbleColor.luminance() > 0.5f) { - if (MaterialTheme.colorScheme.onSurface.luminance() < 0.5f) { - MaterialTheme.colorScheme.onSurface + if (onSurface.luminance() < 0.5f) { + onSurface } else { Color(0xFF1A1A24) } } else { - if (MaterialTheme.colorScheme.onSurface.luminance() > 0.5f) { - MaterialTheme.colorScheme.onSurface + if (onSurface.luminance() > 0.5f) { + onSurface } else { Color(0xFFE8E6EE) } 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 2b3bf781..8f3cf332 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 @@ -75,7 +75,6 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -140,8 +139,9 @@ fun ChatScreen( // during composition — BEFORE any WebSocket event (GatewayReady) can be // processed. This ensures GatewayReady sees the pending session and // resumes it instead of creating a new empty chat (issue #240). - SideEffect { + remember(sessionId) { viewModel.initialSessionId = sessionId + true } // Switch to the session from a notification tap or history screen when provided. @@ -245,15 +245,21 @@ fun ChatScreen( ) } + val backgroundColor = MaterialTheme.colorScheme.background + val surfaceVariantColor = MaterialTheme.colorScheme.surfaceVariant + val primaryColor = MaterialTheme.colorScheme.primary + val backgroundGradient = - Brush.verticalGradient( - colors = - listOf( - MaterialTheme.colorScheme.background, - MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), - MaterialTheme.colorScheme.primary.copy(alpha = 0.05f), - ), - ) + remember(backgroundColor, surfaceVariantColor, primaryColor) { + Brush.verticalGradient( + colors = + listOf( + backgroundColor, + surfaceVariantColor.copy(alpha = 0.3f), + primaryColor.copy(alpha = 0.05f), + ), + ) + } // Scroll to current search match LaunchedEffect(state.isSearchActive, state.currentSearchMatchIndex, state.searchMatchIndices) { 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 a4f69d36..aa16743c 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 @@ -1105,13 +1105,13 @@ class ChatViewModel( while (true) { delay(PENDING_REQUEST_TIMEOUT_MS) val now = System.currentTimeMillis() - val stale = - pendingRequests.entries.filter { - now - it.value.createdAt > PENDING_REQUEST_TIMEOUT_MS + val iterator = pendingRequests.entries.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + if (now - entry.value.createdAt > PENDING_REQUEST_TIMEOUT_MS) { + iterator.remove() + Log.w(TAG, "Request timed out: ${entry.value.method} (id=${entry.key})") } - for (entry in stale) { - pendingRequests.remove(entry.key) - Log.w(TAG, "Request timed out: ${entry.value.method} (id=${entry.key})") } } }