@@ -19,9 +19,11 @@ import androidx.compose.material.icons.Icons
19import androidx.compose.material.icons.automirrored.filled.ArrowBack
19import androidx.compose.material.icons.automirrored.filled.ArrowBack
20import androidx.compose.material.icons.filled.Add
20import androidx.compose.material.icons.filled.Add
21import androidx.compose.material.icons.filled.ChatBubbleOutline
21import androidx.compose.material.icons.filled.ChatBubbleOutline
22+import androidx.compose.material.icons.filled.ChevronRight
22import androidx.compose.material.icons.filled.DeleteOutline
23import androidx.compose.material.icons.filled.DeleteOutline
23import androidx.compose.material.icons.filled.Refresh
24import androidx.compose.material.icons.filled.Refresh
24import androidx.compose.material3.AlertDialog
25import androidx.compose.material3.AlertDialog
26+import androidx.compose.material3.CircularProgressIndicator
25import androidx.compose.material3.ExperimentalMaterial3Api
27import androidx.compose.material3.ExperimentalMaterial3Api
26import androidx.compose.material3.ExtendedFloatingActionButton
28import androidx.compose.material3.ExtendedFloatingActionButton
27import androidx.compose.material3.HorizontalDivider
29import androidx.compose.material3.HorizontalDivider
@@ -31,6 +33,7 @@ import androidx.compose.material3.LinearProgressIndicator
31import androidx.compose.material3.MaterialTheme
33import androidx.compose.material3.MaterialTheme
32import androidx.compose.material3.OutlinedTextField
34import androidx.compose.material3.OutlinedTextField
33import androidx.compose.material3.Scaffold
35import androidx.compose.material3.Scaffold
36+import androidx.compose.material3.Surface
34import androidx.compose.material3.SwipeToDismissBox
37import androidx.compose.material3.SwipeToDismissBox
35import androidx.compose.material3.SwipeToDismissBoxValue
38import androidx.compose.material3.SwipeToDismissBoxValue
36import androidx.compose.material3.Text
39import androidx.compose.material3.Text
@@ -117,6 +120,9 @@ fun SessionsScreen(state: UiState, dispatch: (Event) -> Unit) {
118 ) { padding ->
121 ) { padding ->
119 Column(Modifier.fillMaxSize().padding(padding)) {
122 Column(Modifier.fillMaxSize().padding(padding)) {
123+ if (!state.sseConnected) {
124+ ReconnectingBanner()
120 if (state.loading && !isPullRefreshing) {
126 if (state.loading && !isPullRefreshing) {
121 LinearProgressIndicator(Modifier.fillMaxWidth())
127 LinearProgressIndicator(Modifier.fillMaxWidth())
@@ -217,6 +223,12 @@ private fun SessionRow(session: SessionView, onOpen: () -> Unit, onDelete: () ->
217 overflow = TextOverflow.Ellipsis,
223 overflow = TextOverflow.Ellipsis,
227+ Icons.Filled.ChevronRight,
228+ contentDescription = null,
229+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
230+ modifier = Modifier.padding(horizontal = 4.dp),
220 IconButton(onClick = onDelete) {
232 IconButton(onClick = onDelete) {
222 Icons.Filled.DeleteOutline,
234 Icons.Filled.DeleteOutline,
@@ -245,6 +257,32 @@ private fun SwipeDeleteBackground() {
260+/** Shown at the top of the list while the SSE stream is down, since session updates stall too. */
262+private fun ReconnectingBanner() {
264+ modifier = Modifier.fillMaxWidth(),
265+ color = MaterialTheme.colorScheme.tertiaryContainer,
268+ modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 6.dp),
269+ verticalAlignment = Alignment.CenterVertically,
270+ horizontalArrangement = Arrangement.spacedBy(8.dp),
272+ CircularProgressIndicator(
273+ modifier = Modifier.size(14.dp),
275+ color = MaterialTheme.colorScheme.onTertiaryContainer,
278+ text = "Reconnecting…",
279+ style = MaterialTheme.typography.bodySmall,
280+ color = MaterialTheme.colorScheme.onTertiaryContainer,
248/** Shorten a session ID for display: "abc12345…" instead of the full UUID. */
286/** Shorten a session ID for display: "abc12345…" instead of the full UUID. */
249private fun shortSessionId(id: String): String =
287private fun shortSessionId(id: String): String =
250 if (id.length > 12) id.take(8) + "…" else id
288 if (id.length > 12) id.take(8) + "…" else id