1package soy.iko.opencode.ui.file
3import android.content.Intent
4import androidx.compose.foundation.horizontalScroll
5import androidx.compose.foundation.layout.Box
6import androidx.compose.foundation.layout.Column
7import androidx.compose.foundation.layout.Row
8import androidx.compose.foundation.layout.fillMaxSize
9import androidx.compose.foundation.layout.padding
10import androidx.compose.foundation.layout.width
11import androidx.compose.foundation.lazy.LazyColumn
12import androidx.compose.foundation.lazy.items
13import androidx.compose.foundation.rememberScrollState
14import androidx.compose.foundation.verticalScroll
15import androidx.compose.material.icons.Icons
16import androidx.compose.material.icons.automirrored.filled.ArrowBack
17import androidx.compose.material.icons.filled.ContentCopy
18import androidx.compose.material.icons.filled.Share
19import androidx.compose.material3.CircularProgressIndicator
20import androidx.compose.material3.ExperimentalMaterial3Api
21import androidx.compose.material3.Icon
22import androidx.compose.material3.IconButton
23import androidx.compose.material3.MaterialTheme
24import androidx.compose.material3.Scaffold
25import androidx.compose.material3.Text
26import androidx.compose.material3.TopAppBar
27import androidx.compose.runtime.Composable
28import androidx.compose.runtime.getValue
29import androidx.compose.runtime.remember
30import androidx.compose.ui.Alignment
31import androidx.compose.ui.Modifier
32import androidx.compose.ui.platform.LocalContext
33import androidx.compose.ui.text.font.FontFamily
34import androidx.compose.ui.text.style.TextOverflow
35import androidx.compose.ui.unit.dp
36import androidx.lifecycle.compose.collectAsStateWithLifecycle
37import androidx.lifecycle.viewmodel.compose.viewModel
38import soy.iko.opencode.di.AppContainer
39import soy.iko.opencode.ui.components.DiffView
40import soy.iko.opencode.ui.components.copyToClipboard
41import soy.iko.opencode.ui.vmFactory
43@OptIn(ExperimentalMaterial3Api::class)
46 container: AppContainer,
50 val vm: FileViewModel = viewModel(factory = vmFactory { FileViewModel(container, path) })
51 val state by vm.state.collectAsStateWithLifecycle()
52 val context = LocalContext.current
53 val filename = remember(path) { path.substringAfterLast('/') }
58 title = { Text(filename, maxLines = 1, overflow = TextOverflow.Ellipsis) },
60 IconButton(onClick = onBack) {
61 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
65 val content = state.content?.content.orEmpty()
66 if (content.isNotEmpty()) {
67 IconButton(onClick = { copyToClipboard(context, filename, content) }) {
68 Icon(Icons.Filled.ContentCopy, contentDescription = "Copy")
70 IconButton(onClick = {
71 val send = Intent(Intent.ACTION_SEND).apply {
73 putExtra(Intent.EXTRA_SUBJECT, filename)
74 putExtra(Intent.EXTRA_TEXT, content)
76 context.startActivity(Intent.createChooser(send, "Share $filename"))
78 Icon(Icons.Filled.Share, contentDescription = "Share")
85 Box(modifier = Modifier.fillMaxSize().padding(padding)) {
87 state.loading -> CircularProgressIndicator(Modifier.align(Alignment.Center))
88 state.error != null -> Text(
90 modifier = Modifier.align(Alignment.Center).padding(24.dp),
91 color = MaterialTheme.colorScheme.error,
93 state.content?.isBinary == true -> Text(
94 "Binary file — preview not supported.",
95 modifier = Modifier.align(Alignment.Center).padding(24.dp),
96 color = MaterialTheme.colorScheme.onSurfaceVariant,
99 val content = state.content
100 val scrollState = rememberScrollState()
101 if (content?.diff != null && content.diff.isNotBlank()) {
109 val text = content?.content.orEmpty()
110 val lines = remember(text) { text.split("\n") }
111 val gutterWidth = remember(lines.size) {
112 (lines.size.toString().length.coerceAtLeast(3) * 10).dp
117 .verticalScroll(scrollState)
118 .horizontalScroll(rememberScrollState())
121 // Line-number gutter
122 Column(modifier = Modifier.width(gutterWidth)) {
123 lines.forEachIndexed { i, _ ->
126 style = MaterialTheme.typography.bodySmall,
127 fontFamily = FontFamily.Monospace,
128 color = MaterialTheme.colorScheme.onSurfaceVariant,
132 Column(modifier = Modifier.padding(start = 8.dp)) {
133 lines.forEach { line ->
136 style = MaterialTheme.typography.bodySmall,
137 fontFamily = FontFamily.Monospace,
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