1package soy.iko.opencode.ui.server
3import androidx.compose.runtime.Immutable
4import androidx.lifecycle.ViewModel
5import androidx.lifecycle.viewModelScope
6import soy.iko.opencode.data.model.ServerProfile
7import soy.iko.opencode.di.AppContainer
8import kotlinx.coroutines.flow.MutableStateFlow
9import kotlinx.coroutines.flow.StateFlow
10import kotlinx.coroutines.flow.asStateFlow
11import kotlinx.coroutines.flow.first
12import kotlinx.coroutines.launch
13import kotlinx.coroutines.withTimeoutOrNull
14import soy.iko.opencode.R
15import soy.iko.opencode.util.runCatchingCancellable
19data class ServerEditState(
20 val id: String? = null,
21 val label: String = "",
22 val baseUrl: String = "",
23 val username: String = "",
24 val password: String = "",
25 val loaded: Boolean = false,
26 val saving: Boolean = false,
27 val error: String? = null,
29 val canSave: Boolean get() = baseUrl.isNotBlank()
30 val isNew: Boolean get() = id == null
33class ServerEditViewModel(
34 private val container: AppContainer,
35 private val profileId: String?,
38 private val _state = MutableStateFlow(ServerEditState(id = profileId))
39 val state: StateFlow<ServerEditState> = _state.asStateFlow()
42 viewModelScope.launch {
43 if (profileId != null) {
44 val existing = withTimeoutOrNull(5_000) {
45 container.profileStore.profiles.first().firstOrNull { it.id == profileId }
47 if (existing != null) {
48 _state.value = ServerEditState(
50 label = existing.label,
51 baseUrl = existing.baseUrl,
52 username = existing.username.orEmpty(),
53 password = existing.password.orEmpty(),
58 // Profile existed but timed out loading — show an error instead of
59 // silently presenting an empty form that looks like "new server".
60 _state.value = ServerEditState(
63 error = container.string(R.string.error_load_timeout),
67 _state.value = _state.value.copy(loaded = true)
71 fun update(transform: (ServerEditState) -> ServerEditState) {
72 _state.value = transform(_state.value)
75 fun save(onDone: () -> Unit) {
77 if (!s.canSave || s.saving) return
78 _state.value = s.copy(saving = true)
79 viewModelScope.launch {
80 val result = runCatchingCancellable {
81 val existingLastUsed = if (s.id != null) {
82 withTimeoutOrNull(5_000) {
83 container.profileStore.profiles.first()
84 .firstOrNull { it.id == s.id }?.lastUsed
87 container.profileStore.save(
89 id = s.id ?: UUID.randomUUID().toString(),
90 label = s.label.trim(),
91 baseUrl = s.baseUrl.trim(),
92 username = s.username.trim().takeIf { it.isNotBlank() },
93 password = s.password.trim().takeIf { it.isNotEmpty() },
94 lastUsed = existingLastUsed,
98 result.onSuccess { onDone() }
99 .onFailure { _state.value = _state.value.copy(error = container.friendlyError(it), saving = false) }
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