1package soy.iko.opencode.ui.file
3import androidx.lifecycle.ViewModel
4import androidx.lifecycle.viewModelScope
5import soy.iko.opencode.data.model.FileNode
6import soy.iko.opencode.di.AppContainer
7import kotlinx.coroutines.Job
8import kotlinx.coroutines.delay
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.flow.asStateFlow
12import kotlinx.coroutines.launch
14data class FileBrowserState(
15 val path: String = "",
16 val entries: List<FileNode> = emptyList(),
17 val query: String = "",
18 val results: List<String> = emptyList(),
19 val searching: Boolean = false,
20 val loading: Boolean = true,
21 val error: String? = null,
23 val isSearching: Boolean get() = query.isNotBlank()
26class FileBrowserViewModel(private val container: AppContainer) : ViewModel() {
28 private val api get() = container.activeConnection.value?.api
30 private val _state = MutableStateFlow(FileBrowserState())
31 val state: StateFlow<FileBrowserState> = _state.asStateFlow()
33 private var searchJob: Job? = null
37 fun open(path: String) {
38 val client = api ?: run {
39 _state.value = _state.value.copy(loading = false, error = "Not connected")
42 _state.value = _state.value.copy(path = path, loading = true, error = null)
43 viewModelScope.launch {
44 runCatching { client.listDirectory(path) }
45 .onSuccess { entries ->
46 _state.value = _state.value.copy(
47 entries = entries.sortedWith(compareByDescending<FileNode> { it.isDirectory }.thenBy { it.name.lowercase() }),
51 .onFailure { _state.value = _state.value.copy(loading = false, error = it.message ?: "Failed to list $path") }
55 /** Navigate up one path segment. */
57 val current = _state.value.path
58 if (current.isBlank()) return
59 val parent = current.trimEnd('/').substringBeforeLast('/', missingDelimiterValue = "")
63 fun setQuery(query: String) {
64 _state.value = _state.value.copy(query = query)
66 if (query.isBlank()) {
67 _state.value = _state.value.copy(results = emptyList(), searching = false)
70 val client = api ?: return
71 searchJob = viewModelScope.launch {
72 delay(250) // debounce
73 _state.value = _state.value.copy(searching = true)
74 runCatching { client.findFiles(query) }
75 .onSuccess { _state.value = _state.value.copy(results = it, searching = false) }
76 .onFailure { _state.value = _state.value.copy(searching = false, error = it.message) }
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