git.iko.soy/opencode-android
on master help sign in
Eliminate redundant string lookups and substring allocations on the streaming hot path
commit734c312a8f0d559df3ebb3613d8e137455e4a711 authoriko <mail@iko.soy> dateWed 1 Jul 2026, 06:25 (3 months ago) parent59feb55
3 files changed+16−9
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+10−5 app/src/main/java/soy/iko/opencode/ui/chat/PartRenderer.kt+3−2 app/src/main/java/soy/iko/opencode/ui/components/MarkdownText.kt+3−2
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+10−5
@@ -130,6 +130,14 @@ fun ChatScreen(
130130 val shareContext = LocalContext.current
131131 val shareLabel = stringResource(R.string.share)
132132 val defaultShareSubject = stringResource(R.string.share_subject)
133+ val sessionLabel = stringResource(R.string.session)
134+ val defaultModelLabel = stringResource(R.string.default_model)
135+ val subtitle = remember(selectedModel?.modelLabel, selectedAgent, defaultModelLabel) {
136+ buildString {
137+ append(selectedModel?.modelLabel ?: defaultModelLabel)
138+ selectedAgent?.let { append(" · $it") }
139+ }
140+ }
133141
134142 // Connection profile (with auth) for rendering inline image attachments.
135143 // Collect as state so a server switch recomposes the image context.
@@ -197,12 +205,9 @@ fun ChatScreen(
197205 Column(modifier = Modifier
198206 .clickable(enabled = models.isNotEmpty(), role = Role.Button) { showModelPicker = true }
199207 ) {
200− Text(sessionTitle ?: stringResource(R.string.session))
208+ Text(sessionTitle ?: sessionLabel)
201209 Text(
202− buildString {
203− append(selectedModel?.modelLabel ?: stringResource(R.string.default_model))
204− selectedAgent?.let { append(" · $it") }
205− },
210+ subtitle,
206211 style = MaterialTheme.typography.labelSmall,
207212 color = MaterialTheme.colorScheme.onSurfaceVariant,
208213 )
app/src/main/java/soy/iko/opencode/ui/chat/PartRenderer.kt+3−2
@@ -97,6 +97,8 @@ private fun ReasoningBlock(text: String, streaming: Boolean, modifier: Modifier)
9797 var expanded by rememberSaveable { mutableStateOf(false) }
9898 val expandedState = stringResource(R.string.state_expanded)
9999 val collapsedState = stringResource(R.string.state_collapsed)
100+ val thinkingLabel = stringResource(R.string.thinking)
101+ val thoughtsLabel = stringResource(R.string.thoughts)
100102 Column(modifier = modifier.fillMaxWidth()) {
101103 Row(
102104 modifier = Modifier
@@ -110,7 +112,6 @@ private fun ReasoningBlock(text: String, streaming: Boolean, modifier: Modifier)
110112 verticalAlignment = Alignment.CenterVertically,
111113 ) {
112114 if (streaming) {
113− val thinkingLabel = stringResource(R.string.thinking)
114115 CircularProgressIndicator(
115116 Modifier.size(14.dp).semantics { contentDescription = thinkingLabel },
116117 strokeWidth = 2.dp,
@@ -125,7 +126,7 @@ private fun ReasoningBlock(text: String, streaming: Boolean, modifier: Modifier)
125126 )
126127 }
127128 Text(
128− if (streaming) stringResource(R.string.thinking) else stringResource(R.string.thoughts),
129+ if (streaming) thinkingLabel else thoughtsLabel,
129130 style = MaterialTheme.typography.labelMedium,
130131 color = MaterialTheme.colorScheme.onSurfaceVariant,
131132 modifier = Modifier.padding(start = 6.dp),
app/src/main/java/soy/iko/opencode/ui/components/MarkdownText.kt+3−2
@@ -91,12 +91,13 @@ fun MarkdownText(
9191 val markdownState = rememberUpdatedState(markdown)
9292 // Keyed to a content-prefix so switching to a different message resets
9393 // immediately instead of showing the old message for one frame.
94− var renderedContent by remember(markdown.take(32)) { mutableStateOf(markdown) }
94+ val prefix = markdown.take(32)
95+ var renderedContent by remember(prefix) { mutableStateOf(markdown) }
9596 // conflate() coalesces a burst of token updates into a single emission so the
9697 // collector sees only the latest value after the previous delay completes. Plain
9798 // collect (not collectLatest) is critical: collectLatest would cancel the delay
9899 // on every new value, reintroducing the same starvation the keyed effect had.
99− LaunchedEffect(markdown.take(32)) {
100+ LaunchedEffect(prefix) {
100101 snapshotFlow { markdownState.value }
101102 .conflate()
102103 .collect { md ->