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.activity.compose.BackHandler
11import androidx.compose.material.icons.Icons
12import androidx.compose.material.icons.automirrored.filled.Chat
13import androidx.compose.material3.Icon
14import androidx.compose.material3.MaterialTheme
15import androidx.compose.material3.Text
16import androidx.compose.runtime.Composable
17import androidx.compose.runtime.LaunchedEffect
18import androidx.compose.runtime.getValue
19import androidx.compose.runtime.key
20import androidx.compose.runtime.mutableStateOf
21import androidx.compose.runtime.saveable.rememberSaveable
22import androidx.compose.runtime.setValue
23import androidx.compose.ui.Alignment
24import androidx.compose.ui.Modifier
25import androidx.compose.ui.res.stringResource
26import androidx.compose.ui.unit.dp
27import androidx.lifecycle.compose.collectAsStateWithLifecycle
28import soy.iko.opencode.R
29import soy.iko.opencode.di.AppContainer
30import soy.iko.opencode.ui.chat.ChatScreen
33 * Master–detail layout for wide screens (≥ 840dp, e.g. tablets / unfolded foldables):
34 * the session list on the left, the selected conversation on the right. On compact
35 * widths the app uses the single-pane [androidx.navigation] back stack instead.
37 * Each side hosts an existing screen (which carries its own Scaffold + app bar), so the
38 * two panes are self-contained. Selecting a session updates the detail pane rather than
39 * pushing a destination; the chat [onBack] clears the selection. The chat composition is
40 * keyed by session id so each conversation gets its own ViewModel.
43fun TwoPaneSessionChat(
44 container: AppContainer,
45 onOpenFiles: () -> Unit,
46 onOpenSettings: () -> Unit,
47 onDisconnect: () -> Unit,
48 modifier: Modifier = Modifier,
50 var selected by rememberSaveable { mutableStateOf<String?>(null) }
51 val pendingOpenSession by container.pendingOpenSession.collectAsStateWithLifecycle()
52 val pendingShare by container.pendingShare.collectAsStateWithLifecycle()
54 // A notification tap / deep link requests a session: open it in the detail pane.
55 LaunchedEffect(pendingOpenSession) {
56 pendingOpenSession?.let {
58 container.consumePendingOpenSession()
62 // Inject a pending share into the currently selected session's draft (if any).
63 // Unlike single-pane mode where the session list is navigated to specifically for
64 // the share, in two-pane mode the list is always visible — so we inject into the
65 // session the user is already viewing rather than whichever one they tap next.
66 LaunchedEffect(pendingShare, selected) {
68 if (pendingShare != null && target != null) {
69 container.consumePendingShare()?.let { container.draftStore.set(target, it) }
73 Row(modifier = modifier.fillMaxSize()) {
77 .widthIn(max = 460.dp),
80 container = container,
81 onOpenSession = { id ->
84 onDisconnect = onDisconnect,
85 onOpenFiles = onOpenFiles,
86 onOpenSettings = onOpenSettings,
90 Box(modifier = Modifier.weight(0.62f).fillMaxSize()) {
91 val sessionId = selected
92 if (sessionId == null) {
95 BackHandler { selected = null }
98 container = container,
99 sessionId = sessionId,
100 onBack = { selected = null },
109private fun EmptyDetail() {
111 modifier = Modifier.fillMaxSize().padding(32.dp),
112 verticalArrangement = Arrangement.Center,
113 horizontalAlignment = Alignment.CenterHorizontally,
116 Icons.AutoMirrored.Filled.Chat,
117 contentDescription = null,
118 tint = MaterialTheme.colorScheme.outline,
121 stringResource(R.string.empty_detail_pane),
122 style = MaterialTheme.typography.bodyLarge,
123 color = MaterialTheme.colorScheme.onSurfaceVariant,
124 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