* fix(#283): apply minor performance optimizations for Chat - Optimized `ChatScreen.kt` background gradient to use `remember` block correctly keyed by individual colors to avoid reallocation on recomposition. - Removed redundant `luminance()` calculation caching inside `ChatBubble.kt` to preserve dynamic theme changes correctly while keeping code concise. - Refactored `pendingRequests` timeout loop in `ChatViewModel.kt` to use `iterator.remove()` instead of generating a new filtered list. - Converted `SideEffect` for `initialSessionId` assignment in `ChatScreen` to `remember` block to guarantee synchronous setup before WebSocket processes it. - Initialized an explicit OkHttp `ConnectionPool` in `ApiClient` to survive client rebuilds. These changes resolve Low-severity performance findings (PERF-22 to PERF-26) mentioned in issue #283 while ensuring maintainability and preserving dynamic theming. * fix(#283): apply minor performance optimizations for Chat - Optimized `ChatScreen.kt` background gradient to use `remember` block correctly keyed by individual colors to avoid reallocation on recomposition. - Removed redundant `luminance()` calculation caching inside `ChatBubble.kt` to preserve dynamic theme changes correctly while keeping code concise. - Refactored `pendingRequests` timeout loop in `ChatViewModel.kt` to use `iterator.remove()` instead of generating a new filtered list. - Converted `SideEffect` for `initialSessionId` assignment in `ChatScreen` to `remember` block to guarantee synchronous setup before WebSocket processes it. - Initialized an explicit OkHttp `ConnectionPool` in `ApiClient` to survive client rebuilds. These changes resolve Low-severity performance findings (PERF-22 to PERF-26) mentioned in issue #283 while ensuring maintainability and preserving dynamic theming.
This commit is contained in:
4 files changed
+35
-24
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user