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