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