git.iko.soy/opencode-android
on master help sign in
Fix missing reconnect spinner in chat, silent reconnect with no saved servers, stale sessions after failed server switch restore, and runCatching swallowing cancellation in share intents
commitdcd2218a21b4e0548713316afd20ed895763d552 authoriko <mail@iko.soy> dateTue 30 Jun 2026, 15:57 (3 months ago) parentd4cde6a
6 files changed+31−6
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+11−2 app/src/main/java/soy/iko/opencode/ui/chat/ChatViewModel.kt+5−1 app/src/main/java/soy/iko/opencode/ui/file/FileViewScreen.kt+2−1 app/src/main/java/soy/iko/opencode/ui/session/SessionListViewModel.kt+11−1 app/src/main/java/soy/iko/opencode/ui/settings/DiagnosticsScreen.kt+1−1 app/src/main/res/values/strings.xml+1
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+11−2
@@ -123,6 +123,7 @@ fun ChatScreen(
123123 val sessionDeleted by vm.sessionDeleted.collectAsStateWithLifecycle()
124124 val failedDraft by vm.failedDraft.collectAsStateWithLifecycle()
125125 val draft by vm.draft.collectAsStateWithLifecycle()
126+ val reconnecting by vm.reconnecting.collectAsStateWithLifecycle()
126127 val haptics = LocalHapticFeedback.current
127128 val scope = rememberCoroutineScope()
128129 val shareContext = LocalContext.current
@@ -309,8 +310,16 @@ fun ChatScreen(
309310 color = MaterialTheme.colorScheme.error,
310311 )
311312 Spacer(Modifier.size(12.dp))
312− Button(onClick = { vm.reconnect() }) {
313− Text(stringResource(R.string.reconnect))
313+ Button(onClick = { vm.reconnect() }, enabled = !reconnecting) {
314+ if (reconnecting) {
315+ CircularProgressIndicator(
316+ Modifier.size(20.dp),
317+ color = MaterialTheme.colorScheme.onPrimary,
318+ strokeWidth = 2.dp,
319+ )
320+ } else {
321+ Text(stringResource(R.string.reconnect))
322+ }
314323 }
315324 }
316325 } else {
app/src/main/java/soy/iko/opencode/ui/chat/ChatViewModel.kt+5−1
@@ -440,7 +440,11 @@ class ChatViewModel(
440440 try {
441441 val recent = runCatchingCancellable { container.profileStore.profiles.first() }
442442 .getOrDefault(emptyList())
443− .firstOrNull() ?: return@launch
443+ .firstOrNull()
444+ if (recent == null) {
445+ _error.value = container.string(R.string.no_servers_to_reconnect)
446+ return@launch
447+ }
444448 runCatchingCancellable {
445449 val conn = container.connect(recent)
446450 conn.api.ping()
app/src/main/java/soy/iko/opencode/ui/file/FileViewScreen.kt+2−1
@@ -43,6 +43,7 @@ import soy.iko.opencode.R
4343import soy.iko.opencode.ui.components.DiffView
4444import soy.iko.opencode.ui.components.copyToClipboard
4545import soy.iko.opencode.ui.vmFactory
46+import soy.iko.opencode.util.runCatchingCancellable
4647
4748@OptIn(ExperimentalMaterial3Api::class)
4849@Composable
@@ -78,7 +79,7 @@ fun FileViewScreen(
7879 putExtra(Intent.EXTRA_SUBJECT, filename)
7980 putExtra(Intent.EXTRA_TEXT, content)
8081 }
81− runCatching { context.startActivity(Intent.createChooser(send, shareLabel)) }
82+ runCatchingCancellable { context.startActivity(Intent.createChooser(send, shareLabel)) }
8283 .onFailure {
8384 showToast(context, context.getString(R.string.no_share_app))
8485 }
app/src/main/java/soy/iko/opencode/ui/session/SessionListViewModel.kt+11−1
@@ -317,7 +317,17 @@ class SessionListViewModel(private val container: AppContainer) : ViewModel() {
317317 // user doesn't lose unread badges on a failed server switch.
318318 savedUnread.forEach { container.restoreUnread(it) }
319319 if (restored.isSuccess) {
320− _state.update { it.copy(loading = false, error = container.friendlyError(error)) }
320+ // Clear stale sessions from the old server; the SSE observer
321+ // will reload from the restored connection via refresh().
322+ _state.update {
323+ SessionListState(
324+ sessions = emptyList(),
325+ previews = emptyMap(),
326+ loading = false,
327+ error = container.friendlyError(error),
328+ )
329+ }
330+ refresh()
321331 } else {
322332 // Restore also failed — the user is now disconnected from
323333 // both servers. Surface both failures so the user understands
app/src/main/java/soy/iko/opencode/ui/settings/DiagnosticsScreen.kt+1−1
@@ -181,7 +181,7 @@ fun DiagnosticsScreen(onBack: () -> Unit) {
181181 putExtra(Intent.EXTRA_SUBJECT, shareLabel2)
182182 putExtra(Intent.EXTRA_TEXT, content.orEmpty())
183183 }
184− runCatching { context.startActivity(Intent.createChooser(send, shareLabel2)) }
184+ runCatchingCancellable { context.startActivity(Intent.createChooser(send, shareLabel2)) }
185185 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
186186 }) { Text(stringResource(R.string.share)) }
187187 Spacer(Modifier.size(8.dp))
app/src/main/res/values/strings.xml+1
@@ -36,6 +36,7 @@
3636 <string name="connection_failed">Connection failed — check credentials</string>
3737 <string name="reconnect">Reconnect</string>
3838 <string name="not_connected">Not connected.</string>
39+ <string name="no_servers_to_reconnect">No saved server to reconnect to.</string>
3940
4041 <!-- Server list -->
4142 <string name="servers_title">opencode servers</string>