1package soy.iko.opencode.ui.file
3import androidx.compose.runtime.Immutable
4import androidx.lifecycle.ViewModel
5import androidx.lifecycle.viewModelScope
6import soy.iko.opencode.data.model.FileContent
7import soy.iko.opencode.di.AppContainer
8import soy.iko.opencode.R
9import kotlinx.coroutines.ExperimentalCoroutinesApi
10import kotlinx.coroutines.flow.MutableStateFlow
11import kotlinx.coroutines.flow.StateFlow
12import kotlinx.coroutines.flow.asStateFlow
13import kotlinx.coroutines.flow.collectLatest
14import kotlinx.coroutines.flow.merge
15import kotlinx.coroutines.flow.update
16import kotlinx.coroutines.launch
17import soy.iko.opencode.util.runCatchingCancellable
20data class FileViewState(
21 val loading: Boolean = true,
22 val content: FileContent? = null,
23 val error: String? = null,
26@OptIn(ExperimentalCoroutinesApi::class)
28 private val container: AppContainer,
29 private val path: String,
32 private val _state = MutableStateFlow(FileViewState())
33 val state: StateFlow<FileViewState> = _state.asStateFlow()
35 /** Incremented by [reload] to trigger a fresh fetch without waiting for a connection change. */
36 private val _reload = MutableStateFlow(0)
38 fun reload() { _reload.value++ }
41 // Observe the active connection so the file loads (or reloads) when a connection
42 // becomes available — including when the view opens during a reconnect window
43 // where activeConnection.value was momentarily null. collectLatest cancels the
44 // in-flight load if the connection is replaced mid-read. The reload trigger is
45 // merged in so a manual retry (e.g. after a transient error) re-fetches.
46 viewModelScope.launch {
47 merge(container.activeConnection, _reload).collectLatest {
48 val conn = container.activeConnection.value
50 _state.value = FileViewState(loading = false, error = container.string(R.string.not_connected))
53 // Keep any already-loaded content visible during a reload (only the initial
54 // load has null content) so a manual refresh doesn't blank the file — matching
55 // the file browser, which keeps the prior listing while refreshing.
56 _state.update { it.copy(loading = true, error = null) }
57 runCatchingCancellable { conn.api.readFile(path) }
58 .onSuccess { _state.value = FileViewState(loading = false, content = it) }
59 .onFailure { _state.value = FileViewState(loading = false, error = container.friendlyError(it)) }
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