git.iko.soy/parseable-android
on 3b7ea33 help sign in
278 lines 12 kB history raw
1package com.parseable.android.ui.screens.settings
3import androidx.compose.foundation.layout.*
4import androidx.compose.foundation.rememberScrollState
5import androidx.compose.foundation.verticalScroll
6import androidx.compose.material.icons.Icons
7import androidx.compose.material.icons.automirrored.filled.ArrowBack
8import androidx.compose.material.icons.filled.*
9import androidx.compose.material3.*
10import androidx.compose.material3.pulltorefresh.PullToRefreshBox
11import androidx.compose.runtime.*
12import androidx.compose.ui.Alignment
13import androidx.compose.ui.Modifier
14import androidx.compose.ui.text.font.FontWeight
15import androidx.compose.ui.unit.dp
16import androidx.hilt.navigation.compose.hiltViewModel
17import androidx.lifecycle.compose.collectAsStateWithLifecycle
18import com.parseable.android.BuildConfig
19import kotlinx.serialization.json.contentOrNull
20import kotlinx.serialization.json.jsonPrimitive
22@OptIn(ExperimentalMaterial3Api::class)
23@Composable
24fun SettingsScreen(
25 onBack: () -> Unit,
26 onAboutClick: () -> Unit,
27 viewModel: SettingsViewModel = hiltViewModel(),
28) {
29 val state by viewModel.state.collectAsStateWithLifecycle()
31 Scaffold(
32 topBar = {
33 TopAppBar(
34 title = { Text("Settings") },
35 navigationIcon = {
36 IconButton(onClick = onBack) {
37 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
38 }
39 },
40 )
41 },
42 ) { padding ->
43 PullToRefreshBox(
44 isRefreshing = state.isLoading,
45 onRefresh = viewModel::load,
46 modifier = Modifier
47 .fillMaxSize()
48 .padding(padding),
49 ) {
50 Column(
51 modifier = Modifier
52 .fillMaxSize()
53 .verticalScroll(rememberScrollState())
54 .padding(16.dp),
55 verticalArrangement = Arrangement.spacedBy(16.dp),
56 ) {
57 // Server info
58 Card(
59 modifier = Modifier.fillMaxWidth(),
60 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
61 ) {
62 Column(modifier = Modifier.padding(16.dp)) {
63 Row(verticalAlignment = Alignment.CenterVertically) {
64 Icon(
65 Icons.Filled.Dns,
66 contentDescription = "Server connection",
67 tint = MaterialTheme.colorScheme.primary,
68 )
69 Spacer(modifier = Modifier.width(8.dp))
70 Text(
71 text = "Server Connection",
72 style = MaterialTheme.typography.titleMedium,
73 fontWeight = FontWeight.Bold,
74 )
75 }
76 Spacer(modifier = Modifier.height(12.dp))
77 SettingsRow("URL", state.serverUrl)
78 SettingsRow("Username", state.username)
79 SettingsRow("TLS", if (state.useTls) "Enabled" else "Disabled")
80 }
81 }
83 // Loading indicator for server data
84 if (state.isLoading) {
85 Box(
86 modifier = Modifier
87 .fillMaxWidth()
88 .padding(vertical = 8.dp),
89 contentAlignment = Alignment.Center,
90 ) {
91 Row(
92 verticalAlignment = Alignment.CenterVertically,
93 horizontalArrangement = Arrangement.spacedBy(8.dp),
94 ) {
95 CircularProgressIndicator(
96 modifier = Modifier.size(16.dp),
97 strokeWidth = 2.dp,
98 )
99 Text(
100 text = "Loading server info...",
101 style = MaterialTheme.typography.bodySmall,
102 color = MaterialTheme.colorScheme.onSurfaceVariant,
103 )
104 }
105 }
106 }
108 // Error from loading server data
109 state.error?.let { errorText ->
110 Card(
111 colors = CardDefaults.cardColors(
112 containerColor = MaterialTheme.colorScheme.errorContainer,
113 ),
114 modifier = Modifier.fillMaxWidth(),
115 ) {
116 Row(
117 modifier = Modifier.padding(16.dp),
118 verticalAlignment = Alignment.CenterVertically,
119 ) {
120 Icon(
121 Icons.Filled.ErrorOutline,
122 contentDescription = "Error",
123 tint = MaterialTheme.colorScheme.onErrorContainer,
124 )
125 Spacer(modifier = Modifier.width(8.dp))
126 Text(
127 text = errorText,
128 color = MaterialTheme.colorScheme.onErrorContainer,
129 style = MaterialTheme.typography.bodyMedium,
130 )
131 }
132 }
133 }
135 // About info
136 state.aboutInfo?.let { about ->
137 Card(
138 modifier = Modifier.fillMaxWidth(),
139 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
140 ) {
141 Column(modifier = Modifier.padding(16.dp)) {
142 Row(verticalAlignment = Alignment.CenterVertically) {
143 Icon(
144 Icons.Filled.Info,
145 contentDescription = "Server info",
146 tint = MaterialTheme.colorScheme.primary,
147 )
148 Spacer(modifier = Modifier.width(8.dp))
149 Text(
150 text = "Server Info",
151 style = MaterialTheme.typography.titleMedium,
152 fontWeight = FontWeight.Bold,
153 )
154 }
155 Spacer(modifier = Modifier.height(12.dp))
156 about.version?.let { SettingsRow("Version", it) }
157 about.commit?.let { SettingsRow("Commit", it.take(12)) }
158 about.mode?.let { SettingsRow("Mode", it) }
159 about.license?.get("name")?.jsonPrimitive?.contentOrNull?.let { SettingsRow("License", it) }
160 about.deploymentId?.let { SettingsRow("Deployment ID", it.take(12)) }
161 about.queryEngine?.let { SettingsRow("Query Engine", it) }
162 }
163 }
164 }
166 // Users section
167 if (state.users.isNotEmpty()) {
168 Card(
169 modifier = Modifier.fillMaxWidth(),
170 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
171 ) {
172 Column(modifier = Modifier.padding(16.dp)) {
173 Row(verticalAlignment = Alignment.CenterVertically) {
174 Icon(
175 Icons.Filled.People,
176 contentDescription = "Users",
177 tint = MaterialTheme.colorScheme.primary,
178 )
179 Spacer(modifier = Modifier.width(8.dp))
180 Text(
181 text = "Users (${state.users.size})",
182 style = MaterialTheme.typography.titleMedium,
183 fontWeight = FontWeight.Bold,
184 )
185 }
186 Spacer(modifier = Modifier.height(12.dp))
187 state.users.forEach { user ->
188 Text(
189 text = user,
190 style = MaterialTheme.typography.bodyMedium,
191 modifier = Modifier.padding(vertical = 4.dp),
192 )
193 }
194 }
195 }
196 }
198 // App info
199 Card(
200 modifier = Modifier.fillMaxWidth(),
201 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
202 ) {
203 Column(modifier = Modifier.padding(16.dp)) {
204 Row(verticalAlignment = Alignment.CenterVertically) {
205 Icon(
206 Icons.Filled.PhoneAndroid,
207 contentDescription = "App info",
208 tint = MaterialTheme.colorScheme.primary,
209 )
210 Spacer(modifier = Modifier.width(8.dp))
211 Text(
212 text = "App",
213 style = MaterialTheme.typography.titleMedium,
214 fontWeight = FontWeight.Bold,
215 )
216 }
217 Spacer(modifier = Modifier.height(12.dp))
218 SettingsRow("App Version", BuildConfig.VERSION_NAME)
219 SettingsRow("Platform", "Android")
220 }
221 }
223 // About
224 Card(
225 onClick = onAboutClick,
226 modifier = Modifier.fillMaxWidth(),
227 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
228 ) {
229 Row(
230 modifier = Modifier
231 .fillMaxWidth()
232 .padding(16.dp),
233 verticalAlignment = Alignment.CenterVertically,
234 ) {
235 Icon(
236 Icons.Filled.Info,
237 contentDescription = "About",
238 tint = MaterialTheme.colorScheme.primary,
239 )
240 Spacer(modifier = Modifier.width(8.dp))
241 Text(
242 text = "About",
243 style = MaterialTheme.typography.titleMedium,
244 fontWeight = FontWeight.Bold,
245 modifier = Modifier.weight(1f),
246 )
247 Icon(
248 Icons.Filled.ChevronRight,
249 contentDescription = null,
250 tint = MaterialTheme.colorScheme.onSurfaceVariant,
251 )
252 }
253 }
254 }
255 }
256 }
259@Composable
260private fun SettingsRow(label: String, value: String) {
261 Row(
262 modifier = Modifier
263 .fillMaxWidth()
264 .padding(vertical = 4.dp),
265 horizontalArrangement = Arrangement.SpaceBetween,
266 ) {
267 Text(
268 text = label,
269 style = MaterialTheme.typography.bodyMedium,
270 color = MaterialTheme.colorScheme.onSurfaceVariant,
271 )
272 Text(
273 text = value,
274 style = MaterialTheme.typography.bodyMedium,
275 fontWeight = FontWeight.Medium,
276 )
277 }