git.iko.soy/opencode-android
on 325e359 help sign in
222 lines 9.9 kB history raw
1package soy.iko.opencode.ui.settings
3import android.content.pm.PackageManager
4import android.os.Build
5import androidx.compose.foundation.clickable
6import androidx.compose.foundation.layout.Arrangement
7import androidx.compose.foundation.layout.Column
8import androidx.compose.foundation.layout.Row
9import androidx.compose.foundation.layout.Spacer
10import androidx.compose.foundation.layout.defaultMinSize
11import androidx.compose.foundation.layout.fillMaxWidth
12import androidx.compose.foundation.layout.padding
13import androidx.compose.foundation.layout.size
14import androidx.compose.foundation.layout.widthIn
15import androidx.compose.foundation.selection.selectable
16import androidx.compose.foundation.selection.toggleable
17import androidx.compose.material.icons.Icons
18import androidx.compose.material.icons.automirrored.filled.ArrowBack
19import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
20import androidx.compose.material.icons.filled.BugReport
21import androidx.compose.material.icons.filled.Dns
22import androidx.compose.material3.ExperimentalMaterial3Api
23import androidx.compose.material3.HorizontalDivider
24import androidx.compose.material3.Icon
25import androidx.compose.material3.IconButton
26import androidx.compose.material3.MaterialTheme
27import androidx.compose.material3.RadioButton
28import androidx.compose.material3.Scaffold
29import androidx.compose.material3.Switch
30import androidx.compose.material3.Text
31import androidx.compose.material3.TextButton
32import androidx.compose.material3.TopAppBar
33import androidx.compose.runtime.Composable
34import androidx.compose.runtime.getValue
35import androidx.compose.runtime.remember
36import androidx.compose.runtime.rememberCoroutineScope
37import androidx.compose.ui.Alignment
38import androidx.compose.ui.Modifier
39import androidx.compose.ui.platform.LocalContext
40import androidx.compose.ui.res.stringResource
41import androidx.compose.ui.semantics.Role
42import androidx.compose.ui.semantics.heading
43import androidx.compose.ui.semantics.role
44import androidx.compose.ui.semantics.semantics
45import androidx.compose.ui.unit.dp
46import androidx.lifecycle.compose.collectAsStateWithLifecycle
47import kotlinx.coroutines.launch
48import soy.iko.opencode.data.repo.ThemeMode
49import soy.iko.opencode.di.AppContainer
50import soy.iko.opencode.R
51import soy.iko.opencode.util.runCatchingCancellable
53@OptIn(ExperimentalMaterial3Api::class)
54@Composable
55fun SettingsScreen(container: AppContainer, onBack: () -> Unit, onManageServers: () -> Unit, onOpenDiagnostics: () -> Unit) {
56 val scope = rememberCoroutineScope()
57 val themeMode by container.settingsStore.themeMode.collectAsStateWithLifecycle(initialValue = ThemeMode.SYSTEM)
58 val dynamicColor by container.settingsStore.dynamicColor.collectAsStateWithLifecycle(initialValue = true)
59 val activeProfile = container.activeConnection.collectAsStateWithLifecycle().value?.profile
60 val context = LocalContext.current
61 val unknownVersion = stringResource(R.string.unknown_version)
62 val versionName = remember {
63 runCatching {
64 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
65 context.packageManager.getPackageInfo(
66 context.packageName,
67 PackageManager.PackageInfoFlags.of(0),
68 ).versionName
69 } else {
70 @Suppress("DEPRECATION")
71 context.packageManager.getPackageInfo(context.packageName, 0).versionName
72 }
73 }.getOrNull() ?: unknownVersion
74 }
76 Scaffold(
77 topBar = {
78 TopAppBar(
79 title = { Text(stringResource(R.string.settings)) },
80 navigationIcon = {
81 IconButton(onClick = onBack) {
82 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
83 }
84 },
85 )
86 },
87 ) { padding ->
88 Column(modifier = Modifier.padding(padding).widthIn(max = 600.dp).padding(16.dp)) {
89 Text(
90 stringResource(R.string.appearance),
91 style = MaterialTheme.typography.titleSmall,
92 color = MaterialTheme.colorScheme.primary,
93 modifier = Modifier.semantics { heading() },
94 )
95 ThemeMode.entries.forEach { mode ->
96 ThemeRow(
97 mode = mode,
98 selected = themeMode == mode,
99 onSelect = { scope.launch { runCatchingCancellable { container.settingsStore.setThemeMode(mode) } } },
100 )
101 }
103 Spacer(Modifier.size(8.dp))
104 Row(
105 modifier = Modifier
106 .fillMaxWidth()
107 .toggleable(
108 value = dynamicColor,
109 onValueChange = { scope.launch { runCatchingCancellable { container.settingsStore.setDynamicColor(it) } } },
110 role = Role.Switch,
111 )
112 .padding(vertical = 8.dp),
113 verticalAlignment = Alignment.CenterVertically,
114 ) {
115 Column(modifier = Modifier.weight(1f)) {
116 Text(stringResource(R.string.dynamic_color), style = MaterialTheme.typography.bodyLarge)
117 Text(
118 stringResource(R.string.dynamic_color_desc),
119 style = MaterialTheme.typography.bodySmall,
120 color = MaterialTheme.colorScheme.onSurfaceVariant,
121 )
122 }
123 Switch(
124 checked = dynamicColor,
125 onCheckedChange = null,
126 )
127 }
129 HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
131 Text(
132 stringResource(R.string.connection),
133 style = MaterialTheme.typography.titleSmall,
134 color = MaterialTheme.colorScheme.primary,
135 modifier = Modifier.semantics { heading() },
136 )
137 if (activeProfile != null) {
138 Text(activeProfile.displayLabel, modifier = Modifier.padding(top = 8.dp))
139 Text(
140 activeProfile.baseUrl + if (activeProfile.hasAuth) stringResource(R.string.server_auth_basic) else "",
141 style = MaterialTheme.typography.bodySmall,
142 color = MaterialTheme.colorScheme.onSurfaceVariant,
143 )
144 } else {
145 Text(
146 stringResource(R.string.not_connected),
147 modifier = Modifier.padding(top = 8.dp),
148 color = MaterialTheme.colorScheme.onSurfaceVariant,
149 )
150 }
151 Row(
152 modifier = Modifier
153 .fillMaxWidth()
154 .defaultMinSize(minHeight = 48.dp)
155 .padding(top = 8.dp)
156 .clickable(role = Role.Button) { onManageServers() }
157 .padding(vertical = 8.dp),
158 verticalAlignment = Alignment.CenterVertically,
159 ) {
160 Icon(Icons.Filled.Dns, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
161 Text(stringResource(R.string.manage_servers), modifier = Modifier.weight(1f).padding(start = 8.dp))
162 Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant)
163 }
165 HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
167 Text(
168 stringResource(R.string.about),
169 style = MaterialTheme.typography.titleSmall,
170 color = MaterialTheme.colorScheme.primary,
171 modifier = Modifier.semantics { heading() },
172 )
173 Text(
174 stringResource(R.string.about_version, versionName),
175 style = MaterialTheme.typography.bodyMedium,
176 modifier = Modifier.padding(top = 8.dp),
177 )
178 Text(
179 stringResource(R.string.about_desc),
180 style = MaterialTheme.typography.bodySmall,
181 color = MaterialTheme.colorScheme.onSurfaceVariant,
182 modifier = Modifier.padding(top = 4.dp),
183 )
185 Spacer(Modifier.size(16.dp))
186 Row(
187 modifier = Modifier
188 .fillMaxWidth()
189 .defaultMinSize(minHeight = 48.dp)
190 .clickable(role = Role.Button) { onOpenDiagnostics() }
191 .padding(vertical = 8.dp),
192 verticalAlignment = Alignment.CenterVertically,
193 ) {
194 Icon(Icons.Filled.BugReport, contentDescription = null, tint = MaterialTheme.colorScheme.error)
195 Text(stringResource(R.string.diagnostics), modifier = Modifier.weight(1f).padding(start = 8.dp))
196 Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant)
197 }
198 }
199 }
202@Composable
203private fun ThemeRow(mode: ThemeMode, selected: Boolean, onSelect: () -> Unit) {
204 Row(
205 modifier = Modifier
206 .fillMaxWidth()
207 .selectable(selected = selected, onClick = onSelect)
208 .padding(vertical = 8.dp),
209 horizontalArrangement = Arrangement.spacedBy(8.dp),
210 verticalAlignment = Alignment.CenterVertically,
211 ) {
212 RadioButton(selected = selected, onClick = null)
213 Text(
214 when (mode) {
215 ThemeMode.SYSTEM -> stringResource(R.string.theme_system)
216 ThemeMode.LIGHT -> stringResource(R.string.theme_light)
217 ThemeMode.DARK -> stringResource(R.string.theme_dark)
218 },
219 style = MaterialTheme.typography.bodyLarge,
220 )
221 }