1package soy.iko.opencode.ui.session
3import androidx.compose.foundation.layout.Arrangement
4import androidx.compose.foundation.layout.Box
5import androidx.compose.foundation.layout.Column
6import androidx.compose.foundation.layout.Row
7import androidx.compose.foundation.layout.fillMaxSize
8import androidx.compose.foundation.layout.padding
9import androidx.compose.foundation.layout.widthIn
10import androidx.compose.material.icons.Icons
11import androidx.compose.material.icons.automirrored.filled.Chat
12import androidx.compose.material3.Icon
13import androidx.compose.material3.MaterialTheme
14import androidx.compose.material3.Text
15import androidx.compose.runtime.Composable
16import androidx.compose.runtime.LaunchedEffect
17import androidx.compose.runtime.getValue
18import androidx.compose.runtime.key
19import androidx.compose.runtime.mutableStateOf
20import androidx.compose.runtime.saveable.rememberSaveable
21import androidx.compose.runtime.setValue
22import androidx.compose.ui.Alignment
23import androidx.compose.ui.Modifier
24import androidx.compose.ui.res.stringResource
25import androidx.compose.ui.unit.dp
26import androidx.lifecycle.compose.collectAsStateWithLifecycle
27import soy.iko.opencode.R
28import soy.iko.opencode.di.AppContainer
29import soy.iko.opencode.ui.chat.ChatScreen
32 * Master–detail layout for wide screens (≥ 840dp, e.g. tablets / unfolded foldables):
33 * the session list on the left, the selected conversation on the right. On compact
34 * widths the app uses the single-pane [androidx.navigation] back stack instead.
36 * Each side hosts an existing screen (which carries its own Scaffold + app bar), so the
37 * two panes are self-contained. Selecting a session updates the detail pane rather than
38 * pushing a destination; the chat [onBack] clears the selection. The chat composition is
39 * keyed by session id so each conversation gets its own ViewModel.
42fun TwoPaneSessionChat(
43 container: AppContainer,
44 onOpenFiles: () -> Unit,
45 onOpenSettings: () -> Unit,
46 onDisconnect: () -> Unit,
47 modifier: Modifier = Modifier,
49 var selected by rememberSaveable { mutableStateOf<String?>(null) }
50 val pendingOpenSession by container.pendingOpenSession.collectAsStateWithLifecycle()
52 // A notification tap / deep link requests a session: open it in the detail pane.
53 LaunchedEffect(pendingOpenSession) {
54 pendingOpenSession?.let {
56 container.consumePendingOpenSession()
60 Row(modifier = modifier.fillMaxSize()) {
64 .widthIn(max = 460.dp),
67 container = container,
68 onOpenSession = { id -> selected = id },
69 onDisconnect = onDisconnect,
70 onOpenFiles = onOpenFiles,
71 onOpenSettings = onOpenSettings,
75 Box(modifier = Modifier.weight(0.62f).fillMaxSize()) {
76 val sessionId = selected
77 if (sessionId == null) {
82 container = container,
83 sessionId = sessionId,
84 onBack = { selected = null },
93private fun EmptyDetail() {
95 modifier = Modifier.fillMaxSize().padding(32.dp),
96 verticalArrangement = Arrangement.Center,
97 horizontalAlignment = Alignment.CenterHorizontally,
100 Icons.AutoMirrored.Filled.Chat,
101 contentDescription = null,
102 tint = MaterialTheme.colorScheme.outline,
105 stringResource(R.string.empty_detail_pane),
106 style = MaterialTheme.typography.bodyLarge,
107 color = MaterialTheme.colorScheme.onSurfaceVariant,
108 modifier = Modifier.padding(top = 12.dp),
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list