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
dcd2218iko
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+11−2
@@ -123,6 +123,7 @@ fun ChatScreen(
123 val sessionDeleted by vm.sessionDeleted.collectAsStateWithLifecycle()
123 val sessionDeleted by vm.sessionDeleted.collectAsStateWithLifecycle()
124 val failedDraft by vm.failedDraft.collectAsStateWithLifecycle()
124 val failedDraft by vm.failedDraft.collectAsStateWithLifecycle()
125 val draft by vm.draft.collectAsStateWithLifecycle()
125 val draft by vm.draft.collectAsStateWithLifecycle()
126+ val reconnecting by vm.reconnecting.collectAsStateWithLifecycle()
126 val haptics = LocalHapticFeedback.current
127 val haptics = LocalHapticFeedback.current
127 val scope = rememberCoroutineScope()
128 val scope = rememberCoroutineScope()
128 val shareContext = LocalContext.current
129 val shareContext = LocalContext.current
@@ -309,8 +310,16 @@ fun ChatScreen(
309 color = MaterialTheme.colorScheme.error,
310 color = MaterialTheme.colorScheme.error,
310 )
311 )
311 Spacer(Modifier.size(12.dp))
312 Spacer(Modifier.size(12.dp))
312− Button(onClick = { vm.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 {
313− Text(stringResource(R.string.reconnect))
321+ Text(stringResource(R.string.reconnect))
322+ }
314 }
323 }
315 }
324 }
316 } else {
325 } else {
app/src/main/java/soy/iko/opencode/ui/chat/ChatViewModel.kt+5−1
@@ -440,7 +440,11 @@ class ChatViewModel(
440 try {
440 try {
441 val recent = runCatchingCancellable { container.profileStore.profiles.first() }
441 val recent = runCatchingCancellable { container.profileStore.profiles.first() }
442 .getOrDefault(emptyList())
442 .getOrDefault(emptyList())
443+ .firstOrNull()
444+ if (recent == null) {
445+ _error.value = container.string(R.string.no_servers_to_reconnect)
443− .firstOrNull() ?: return@launch
446+ return@launch
447+ }
444 runCatchingCancellable {
448 runCatchingCancellable {
445 val conn = container.connect(recent)
449 val conn = container.connect(recent)
446 conn.api.ping()
450 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
43import soy.iko.opencode.ui.components.DiffView
43import soy.iko.opencode.ui.components.DiffView
44import soy.iko.opencode.ui.components.copyToClipboard
44import soy.iko.opencode.ui.components.copyToClipboard
45import soy.iko.opencode.ui.vmFactory
45import soy.iko.opencode.ui.vmFactory
46+import soy.iko.opencode.util.runCatchingCancellable
46
47
47@OptIn(ExperimentalMaterial3Api::class)
48@OptIn(ExperimentalMaterial3Api::class)
48@Composable
49@Composable
@@ -78,7 +79,7 @@ fun FileViewScreen(
78 putExtra(Intent.EXTRA_SUBJECT, filename)
79 putExtra(Intent.EXTRA_SUBJECT, filename)
79 putExtra(Intent.EXTRA_TEXT, content)
80 putExtra(Intent.EXTRA_TEXT, content)
80 }
81 }
81− runCatching { context.startActivity(Intent.createChooser(send, shareLabel)) }
82+ runCatchingCancellable { context.startActivity(Intent.createChooser(send, shareLabel)) }
82 .onFailure {
83 .onFailure {
83 showToast(context, context.getString(R.string.no_share_app))
84 showToast(context, context.getString(R.string.no_share_app))
84 }
85 }
app/src/main/java/soy/iko/opencode/ui/session/SessionListViewModel.kt+11−1
@@ -317,7 +317,17 @@ class SessionListViewModel(private val container: AppContainer) : ViewModel() {
317 // user doesn't lose unread badges on a failed server switch.
317 // user doesn't lose unread badges on a failed server switch.
318 savedUnread.forEach { container.restoreUnread(it) }
318 savedUnread.forEach { container.restoreUnread(it) }
319 if (restored.isSuccess) {
319 if (restored.isSuccess) {
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,
320− _state.update { it.copy(loading = false, error = container.friendlyError(error)) }
327+ error = container.friendlyError(error),
328+ )
329+ }
330+ refresh()
321 } else {
331 } else {
322 // Restore also failed — the user is now disconnected from
332 // Restore also failed — the user is now disconnected from
323 // both servers. Surface both failures so the user understands
333 // 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) {
181 putExtra(Intent.EXTRA_SUBJECT, shareLabel2)
181 putExtra(Intent.EXTRA_SUBJECT, shareLabel2)
182 putExtra(Intent.EXTRA_TEXT, content.orEmpty())
182 putExtra(Intent.EXTRA_TEXT, content.orEmpty())
183 }
183 }
184− runCatching { context.startActivity(Intent.createChooser(send, shareLabel2)) }
184+ runCatchingCancellable { context.startActivity(Intent.createChooser(send, shareLabel2)) }
185 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
185 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
186 }) { Text(stringResource(R.string.share)) }
186 }) { Text(stringResource(R.string.share)) }
187 Spacer(Modifier.size(8.dp))
187 Spacer(Modifier.size(8.dp))
app/src/main/res/values/strings.xml+1
@@ -36,6 +36,7 @@
36 <string name="connection_failed">Connection failed — check credentials</string>
36 <string name="connection_failed">Connection failed — check credentials</string>
37 <string name="reconnect">Reconnect</string>
37 <string name="reconnect">Reconnect</string>
38 <string name="not_connected">Not connected.</string>
38 <string name="not_connected">Not connected.</string>
39+ <string name="no_servers_to_reconnect">No saved server to reconnect to.</string>
39
40
40 <!-- Server list -->
41 <!-- Server list -->
41 <string name="servers_title">opencode servers</string>
42 <string name="servers_title">opencode servers</string>