git.iko.soy/opencode-android
on d643c13 help sign in
150 lines 6.8 kB history raw
1package soy.iko.opencode.ui.server
3import androidx.compose.foundation.layout.Arrangement
4import androidx.compose.foundation.layout.Box
5import androidx.compose.foundation.layout.Column
6import androidx.compose.foundation.layout.fillMaxSize
7import androidx.compose.foundation.layout.fillMaxWidth
8import androidx.compose.foundation.layout.imePadding
9import androidx.compose.foundation.layout.padding
10import androidx.compose.foundation.layout.size
11import androidx.compose.foundation.layout.widthIn
12import androidx.compose.foundation.rememberScrollState
13import androidx.compose.foundation.text.KeyboardActions
14import androidx.compose.foundation.text.KeyboardOptions
15import androidx.compose.foundation.verticalScroll
16import androidx.compose.material.icons.Icons
17import androidx.compose.material.icons.automirrored.filled.ArrowBack
18import androidx.compose.material3.Button
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.OutlinedTextField
25import androidx.compose.material3.Scaffold
26import androidx.compose.material3.Text
27import androidx.compose.material3.TopAppBar
28import androidx.compose.runtime.Composable
29import androidx.compose.runtime.getValue
30import androidx.compose.ui.Alignment
31import androidx.compose.ui.Modifier
32import androidx.compose.ui.platform.testTag
33import androidx.compose.ui.semantics.contentDescription
34import androidx.compose.ui.semantics.semantics
35import androidx.compose.ui.text.input.ImeAction
36import androidx.compose.ui.text.input.KeyboardType
37import androidx.compose.ui.text.input.PasswordVisualTransformation
38import androidx.compose.ui.res.stringResource
39import androidx.compose.ui.unit.dp
40import androidx.lifecycle.compose.collectAsStateWithLifecycle
41import androidx.lifecycle.viewmodel.compose.viewModel
42import soy.iko.opencode.di.AppContainer
43import soy.iko.opencode.R
44import soy.iko.opencode.ui.vmFactory
46@OptIn(ExperimentalMaterial3Api::class)
47@Composable
48fun ServerEditScreen(
49 container: AppContainer,
50 profileId: String?,
51 onDone: () -> Unit,
52) {
53 val vm: ServerEditViewModel =
54 viewModel(factory = vmFactory { ServerEditViewModel(container, profileId) })
55 val state by vm.state.collectAsStateWithLifecycle()
57 Scaffold(
58 topBar = {
59 TopAppBar(
60 title = { Text(if (state.isNew) stringResource(R.string.add_server) else stringResource(R.string.edit_server)) },
61 navigationIcon = {
62 IconButton(onClick = onDone) {
63 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
64 }
65 },
66 )
67 },
68 ) { padding ->
69 if (!state.loaded) {
70 val loadingLabel = stringResource(R.string.loading)
71 Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {
72 CircularProgressIndicator(Modifier.semantics { contentDescription = loadingLabel })
73 }
74 } else {
75 Column(
76 modifier = Modifier
77 .padding(padding)
78 .imePadding()
79 .widthIn(max = 600.dp)
80 .padding(16.dp)
81 .verticalScroll(rememberScrollState()),
82 verticalArrangement = Arrangement.spacedBy(12.dp),
83 ) {
84 OutlinedTextField(
85 value = state.label,
86 onValueChange = { v -> vm.update { it.copy(label = v) } },
87 label = { Text(stringResource(R.string.label_optional)) },
88 singleLine = true,
89 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
90 modifier = Modifier.fillMaxWidth().testTag("server_label"),
91 )
92 OutlinedTextField(
93 value = state.baseUrl,
94 onValueChange = { v -> vm.update { it.copy(baseUrl = v) } },
95 label = { Text(stringResource(R.string.base_url)) },
96 placeholder = { Text(stringResource(R.string.base_url_hint)) },
97 singleLine = true,
98 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Next),
99 modifier = Modifier.fillMaxWidth().testTag("server_url"),
100 )
101 Text(
102 stringResource(R.string.auth_help),
103 style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
104 )
105 OutlinedTextField(
106 value = state.username,
107 onValueChange = { v -> vm.update { it.copy(username = v) } },
108 label = { Text(stringResource(R.string.username_optional)) },
109 singleLine = true,
110 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
111 modifier = Modifier.fillMaxWidth().testTag("server_username"),
112 )
113 OutlinedTextField(
114 value = state.password,
115 onValueChange = { v -> vm.update { it.copy(password = v) } },
116 label = { Text(stringResource(R.string.password_optional)) },
117 singleLine = true,
118 visualTransformation = PasswordVisualTransformation(),
119 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
120 keyboardActions = KeyboardActions(onDone = { if (state.canSave && !state.saving) vm.save(onDone) }),
121 modifier = Modifier.fillMaxWidth().testTag("server_password"),
122 )
123 Button(
124 onClick = { vm.save(onDone) },
125 enabled = state.canSave && !state.saving,
126 modifier = Modifier.fillMaxWidth(),
127 ) {
128 if (state.saving) {
129 CircularProgressIndicator(
130 modifier = Modifier.size(20.dp),
131 color = MaterialTheme.colorScheme.onPrimary,
132 strokeWidth = 2.dp,
133 )
134 Text(stringResource(R.string.saving), modifier = Modifier.padding(start = 8.dp))
135 } else {
136 Text(stringResource(R.string.save))
137 }
138 }
139 state.error?.let { message ->
140 Text(
141 message,
142 color = androidx.compose.material3.MaterialTheme.colorScheme.error,
143 style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
144 modifier = Modifier.fillMaxWidth(),
145 )
146 }
147 }
148 }
149 }