git.iko.soy/opencode-android
on master help sign in
Narrow recomposition scope on chat, diff, and session list screens
be390fbiko
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+5−1
@@ -347,6 +347,7 @@ fun ChatScreen(
347 modifier = Modifier.align(Alignment.Center),
347 modifier = Modifier.align(Alignment.Center),
348 )
348 )
349 } else {
349 } else {
350+ val lastMessageId = messages.lastOrNull()?.info?.id
350 LazyColumn(
351 LazyColumn(
351 state = listState,
352 state = listState,
352 modifier = Modifier.fillMaxSize(),
353 modifier = Modifier.fillMaxSize(),
@@ -354,7 +355,10 @@ fun ChatScreen(
354 verticalArrangement = Arrangement.spacedBy(16.dp),
355 verticalArrangement = Arrangement.spacedBy(16.dp),
355 ) {
356 ) {
356 items(messages, key = { it.info.id }, contentType = { it.info::class }) { message ->
357 items(messages, key = { it.info.id }, contentType = { it.info::class }) { message ->
358+ // Only the last (streaming) message needs isRunning — it drives the
359+ // reasoning-block spinner. Passing the live flag to every bubble
360+ // makes all visible messages recompose whenever a run starts or stops.
357− MessageBubble(message, isRunning = running, imageContext = imageContext)
361+ MessageBubble(message, isRunning = running && message.info.id == lastMessageId, imageContext = imageContext)
358 }
362 }
359 if (running) {
363 if (running) {
360 item(key = "__typing") {
364 item(key = "__typing") {
app/src/main/java/soy/iko/opencode/ui/components/DiffView.kt+32−27
@@ -129,33 +129,38 @@ fun DiffView(diff: String, modifier: Modifier = Modifier) {
129 }
129 }
130 val visibleLines = if (lines.size <= COLLAPSED_DIFF_LINES || expanded) lines
130 val visibleLines = if (lines.size <= COLLAPSED_DIFF_LINES || expanded) lines
131 else lines.subList(0, COLLAPSED_DIFF_LINES)
131 else lines.subList(0, COLLAPSED_DIFF_LINES)
132+ // One horizontalScroll on the container instead of one per row: each modifier
133+ // adds a layout node + clip + offset pass, so a 200-line collapsed diff was
134+ // paying for 200 of them. The shared scroll state still synchronizes all rows.
135+ Column(modifier = Modifier.horizontalScroll(hScrollState)) {
132− visibleLines.forEach { line ->
136+ visibleLines.forEach { line ->
133− Row(modifier = Modifier.horizontalScroll(hScrollState).padding(horizontal = 10.dp)) {
137+ Row(modifier = Modifier.padding(horizontal = 10.dp)) {
134− when (line) {
138+ when (line) {
135− is DiffLine.Hunk -> Text(
139+ is DiffLine.Hunk -> Text(
136− line.text,
140+ line.text,
137− style = MaterialTheme.typography.bodySmall,
141+ style = MaterialTheme.typography.bodySmall,
138− fontFamily = FontFamily.Monospace,
142+ fontFamily = FontFamily.Monospace,
139− color = MaterialTheme.colorScheme.tertiary,
143+ color = MaterialTheme.colorScheme.tertiary,
140− modifier = Modifier.padding(vertical = 2.dp),
144+ modifier = Modifier.padding(vertical = 2.dp),
141− )
145+ )
142− is DiffLine.FileHeader -> Text(
146+ is DiffLine.FileHeader -> Text(
143− line.text,
147+ line.text,
144− style = MaterialTheme.typography.bodySmall,
148+ style = MaterialTheme.typography.bodySmall,
145− fontFamily = FontFamily.Monospace,
149+ fontFamily = FontFamily.Monospace,
146− color = MaterialTheme.colorScheme.onSurfaceVariant,
150+ color = MaterialTheme.colorScheme.onSurfaceVariant,
147− modifier = Modifier.padding(vertical = 1.dp),
151+ modifier = Modifier.padding(vertical = 1.dp),
148− )
152+ )
149− is DiffLine.Meta -> Text(
153+ is DiffLine.Meta -> Text(
150− line.text,
154+ line.text,
151− style = MaterialTheme.typography.bodySmall,
155+ style = MaterialTheme.typography.bodySmall,
152− fontFamily = FontFamily.Monospace,
156+ fontFamily = FontFamily.Monospace,
153− color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
157+ color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
154− modifier = Modifier.padding(vertical = 1.dp),
158+ modifier = Modifier.padding(vertical = 1.dp),
155− )
159+ )
156− is DiffLine.Add -> DiffRow(line.text, "+", addColor, addText)
160+ is DiffLine.Add -> DiffRow(line.text, "+", addColor, addText)
157− is DiffLine.Remove -> DiffRow(line.text, "-", removeColor, removeText)
161+ is DiffLine.Remove -> DiffRow(line.text, "-", removeColor, removeText)
158− is DiffLine.Context -> DiffRow(line.text, " ", Color.Transparent, MaterialTheme.colorScheme.onSurface)
162+ is DiffLine.Context -> DiffRow(line.text, " ", Color.Transparent, MaterialTheme.colorScheme.onSurface)
163+ }
159 }
164 }
160 }
165 }
161 }
166 }
app/src/main/java/soy/iko/opencode/ui/session/SessionListScreen.kt+1−1
@@ -235,7 +235,7 @@ fun SessionListScreen(
235 singleLine = true,
235 singleLine = true,
236 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
236 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
237 )
237 )
238− val sessions = state.filtered
238+ val sessions = remember(state) { state.filtered }
239 if (sessions.isEmpty()) {
239 if (sessions.isEmpty()) {
240 Text(
240 Text(
241 stringResource(R.string.no_sessions_match, state.query),
241 stringResource(R.string.no_sessions_match, state.query),