@@ -3,32 +3,51 @@ package com.mrboomdev.awery.ui.mobile.components
33import androidx.compose.foundation.layout.Column
44import androidx.compose.foundation.layout.Row
55import androidx.compose.foundation.layout.Spacer
6+ import androidx.compose.foundation.layout.fillMaxWidth
7+ import androidx.compose.foundation.layout.height
68import androidx.compose.foundation.layout.padding
9+ import androidx.compose.foundation.selection.selectable
10+ import androidx.compose.foundation.selection.selectableGroup
711import androidx.compose.foundation.shape.RoundedCornerShape
12+ import androidx.compose.foundation.text.KeyboardActions
13+ import androidx.compose.foundation.text.KeyboardOptions
14+ import androidx.compose.material3.ExperimentalMaterial3Api
815import androidx.compose.material3.MaterialTheme
16+ import androidx.compose.material3.OutlinedTextField
17+ import androidx.compose.material3.RadioButton
918import androidx.compose.material3.Surface
1019import androidx.compose.material3.Switch
1120import androidx.compose.material3.Text
21+ import androidx.compose.material3.TextButton
1222import androidx.compose.material3.TriStateCheckbox
1323import androidx.compose.material3.contentColorFor
1424import androidx.compose.runtime.Composable
25+ import androidx.compose.runtime.derivedStateOf
1526import androidx.compose.runtime.getValue
1627import androidx.compose.runtime.mutableStateOf
1728import androidx.compose.runtime.remember
1829import androidx.compose.runtime.setValue
1930import androidx.compose.ui.Alignment
2031import androidx.compose.ui.Modifier
32+ import androidx.compose.ui.draw.clip
2133import androidx.compose.ui.graphics.Color
2234import androidx.compose.ui.platform.LocalContext
35+ import androidx.compose.ui.res.stringResource
36+ import androidx.compose.ui.semantics.Role
2337import androidx.compose.ui.state.ToggleableState
38+ import androidx.compose.ui.text.input.ImeAction
39+ import androidx.compose.ui.text.input.KeyboardType
2440import androidx.compose.ui.unit.dp
2541import com.mrboomdev.awery.R
26- import com.mrboomdev.awery.app.App.Companion.i18n
27- import com.mrboomdev.awery.app.App.Companion.toast
2842import com.mrboomdev.awery.ext.data.Setting
43+ import com.mrboomdev.awery.platform.PlatformResources.i18n
2944import com.mrboomdev.awery.platform.PlatformSetting
3045import com.mrboomdev.awery.platform.PlatformSettingHandler
46+ import com.mrboomdev.awery.ui.components.MaterialDialog
47+ import com.mrboomdev.awery.utils.compareTo
48+ import com.mrboomdev.awery.utils.toStrippedString
3149
50+ @OptIn(ExperimentalMaterial3Api ::class )
3251@Composable
3352fun MobileSetting (
3453 setting : Setting ,
@@ -37,6 +56,7 @@ fun MobileSetting(
3756) {
3857 var triState by remember { mutableStateOf(setting.value as ? Setting .TriState ? : Setting .TriState .EMPTY ) }
3958 var isChecked by remember { mutableStateOf(setting.value == true ) }
59+ var isDialogShown by remember { mutableStateOf(false ) }
4060 val context = LocalContext .current
4161
4262 Surface (
@@ -67,14 +87,13 @@ fun MobileSetting(
6787
6888 Setting .Type .BOOLEAN -> isChecked = ! isChecked
6989 Setting .Type .TRI_STATE -> triState = triState.next()
70-
71- Setting .Type .FLOAT -> toast(" This action isn't done yet!" )
72- Setting .Type .STRING -> toast(" This action isn't done yet!" )
73- Setting .Type .SELECT -> toast(" This action isn't done yet!" )
74- Setting .Type .INTEGER -> toast(" This action isn't done yet!" )
75- Setting .Type .MULTISELECT -> toast(" This action isn't done yet!" )
76-
7790 Setting .Type .CATEGORY , null -> {}
91+
92+ Setting .Type .FLOAT ,
93+ Setting .Type .STRING ,
94+ Setting .Type .SELECT ,
95+ Setting .Type .INTEGER ,
96+ Setting .Type .MULTISELECT -> isDialogShown = true
7897 }
7998 }
8099 ) {
@@ -90,9 +109,7 @@ fun MobileSetting(
90109 (setting.title ? : (if (setting.description == null ) setting.key else null ))?.let { title ->
91110 Text (
92111 style = MaterialTheme .typography.bodyLarge,
93- text = setting.takeIf { it is PlatformSetting }?.let {
94- i18n<R .string>(title)
95- } ? : title
112+ text = setting.takeIf { it is PlatformSetting }?.let { i18n(title) } ? : title
96113 )
97114 }
98115
@@ -104,10 +121,7 @@ fun MobileSetting(
104121 Text (
105122 style = if (setting.title == null ) MaterialTheme .typography.bodyMedium else MaterialTheme .typography.bodySmall,
106123 color = if (setting.type == Setting .Type .CATEGORY ) MaterialTheme .colorScheme.primary else Color .Unspecified ,
107-
108- text = setting.takeIf { it is PlatformSetting }?.let {
109- i18n<R .string>(description)
110- } ? : description
124+ text = setting.takeIf { it is PlatformSetting }?.let { i18n(description) } ? : description
111125 )
112126 }
113127 }
@@ -133,6 +147,221 @@ fun MobileSetting(
133147 }
134148 }
135149 }
150+
151+ if (isDialogShown) {
152+ var newValue by remember { mutableStateOf(setting.value) }
153+
154+ val isValidValue by remember { derivedStateOf {
155+ when (setting.type) {
156+ Setting .Type .STRING -> {
157+ setting.from?.also { from ->
158+ if (((newValue as ? String? )?.length ? : 0 ) < from) {
159+ return @derivedStateOf false to " This text is too short! Minimum length is ${from.toStrippedString()} ."
160+ }
161+ }
162+
163+ setting.to?.also { to ->
164+ if (((newValue as ? String? )?.length ? : 0 ) > to) {
165+ return @derivedStateOf false to " This text is too long! Maximum length is ${to.toStrippedString()} ."
166+ }
167+ }
168+
169+ true to " "
170+ }
171+
172+ Setting .Type .INTEGER , Setting .Type .FLOAT -> {
173+ setting.from?.also { from ->
174+ if ((newValue as ? Number ? ? : 0 ) < from) {
175+ return @derivedStateOf false to " This number is too short! Minimum length is ${from.toStrippedString()} ."
176+ }
177+ }
178+
179+ setting.to?.also { to ->
180+ if ((newValue as ? Number ? ? : 0 ) > to) {
181+ return @derivedStateOf false to " This number is too long! Maximum length is ${to.toStrippedString()} ."
182+ }
183+ }
184+
185+ if (newValue != null && newValue !is Number ) {
186+ return @derivedStateOf false to " This is not a number!"
187+ }
188+
189+ true to " "
190+ }
191+
192+ else -> true to " No checks can be made on this type."
193+ }
194+ }}
195+
196+ MaterialDialog (
197+ modifier = Modifier .padding(horizontal = 8 .dp),
198+ onDismissRequest = { isDialogShown = false },
199+
200+ title = setting.title?.let { title -> {
201+ Text (
202+ style = MaterialTheme .typography.headlineMedium,
203+ text = setting.takeIf { it is PlatformSetting }?.let { i18n(title) } ? : title
204+ )
205+ }},
206+
207+ dismissButton = {
208+ TextButton (onClick = this @MaterialDialog::requestDismiss) {
209+ Text (text = stringResource(R .string.cancel))
210+ }
211+ },
212+
213+ confirmButton = {
214+ TextButton (onClick = {
215+ if (! isValidValue.first) return @TextButton
216+ setting.value = newValue
217+ requestDismiss()
218+ }) {
219+ Text (text = stringResource(R .string.confirm))
220+ }
221+ }
222+ ) {
223+ Column {
224+ if (setting.description != null ) {
225+ val description = setting.description!!
226+
227+ Text (
228+ text = setting.takeIf { it is PlatformSetting }
229+ ?.let { i18n(description) } ? : description
230+ )
231+ }
232+
233+ when (setting.type) {
234+ Setting .Type .STRING -> {
235+ OutlinedTextField (
236+ isError = isValidValue.first,
237+ label = if (isValidValue.first) null else {{
238+ Text (isValidValue.second)
239+ }},
240+
241+ placeholder = if (setting is PlatformSetting && setting.placeholder != null ) {{
242+ Text (setting.placeholder!! )
243+ }} else null ,
244+
245+ singleLine = true ,
246+ value = newValue?.toString() ? : " " ,
247+ onValueChange = { newValue = it },
248+ keyboardOptions = KeyboardOptions (imeAction = ImeAction .Done ),
249+ keyboardActions = KeyboardActions (onDone = {
250+ if (! isValidValue.first) return @KeyboardActions
251+ setting.value = newValue
252+ requestDismiss()
253+ })
254+ )
255+ }
256+
257+ Setting .Type .INTEGER -> {
258+ OutlinedTextField (
259+ isError = ! isValidValue.first,
260+ label = if (isValidValue.first) null else {{
261+ Text (isValidValue.second)
262+ }},
263+
264+ placeholder = if (setting is PlatformSetting && setting.placeholder != null ) {{
265+ Text (setting.placeholder!! )
266+ }} else null ,
267+
268+ singleLine = true ,
269+ value = newValue?.toString() ? : " " ,
270+
271+ onValueChange = {
272+ newValue = if (it.isBlank()) null else try {
273+ it.toInt()
274+ } catch (e: NumberFormatException ) { it }
275+ },
276+
277+ keyboardOptions = KeyboardOptions (
278+ keyboardType = KeyboardType .Phone ,
279+ imeAction = ImeAction .Done
280+ ),
281+
282+ keyboardActions = KeyboardActions (onDone = {
283+ if (! isValidValue.first) return @KeyboardActions
284+ setting.value = newValue
285+ requestDismiss()
286+ })
287+ )
288+ }
289+
290+ Setting .Type .FLOAT -> {
291+ OutlinedTextField (
292+ isError = ! isValidValue.first,
293+ label = if (isValidValue.first) null else {{
294+ Text (isValidValue.second)
295+ }},
296+
297+ placeholder = if (setting is PlatformSetting && setting.placeholder != null ) {{
298+ Text (setting.placeholder!! )
299+ }} else null ,
300+
301+ singleLine = true ,
302+ value = newValue?.toString() ? : " " ,
303+
304+ onValueChange = {
305+ newValue = if (it.isBlank()) null else try {
306+ it.toFloat()
307+ } catch (e: NumberFormatException ) { it }
308+ },
309+
310+ keyboardOptions = KeyboardOptions (
311+ keyboardType = KeyboardType .Decimal ,
312+ imeAction = ImeAction .Done
313+ ),
314+
315+ keyboardActions = KeyboardActions (onDone = {
316+ setting.value = newValue
317+ requestDismiss()
318+ })
319+ )
320+ }
321+
322+ Setting .Type .SELECT -> {
323+ Column (modifier = Modifier .selectableGroup()) {
324+ for (item in setting.items!! ) {
325+ Row (modifier = Modifier
326+ .clip(RoundedCornerShape (8 .dp))
327+ .fillMaxWidth()
328+ .height(56 .dp)
329+ .selectable(
330+ selected = newValue == item.key,
331+ onClick = { newValue = item.key },
332+ role = Role .RadioButton
333+ ),
334+ verticalAlignment = Alignment .CenterVertically
335+ ) {
336+ RadioButton (
337+ selected = newValue == item.key,
338+ onClick = null
339+ )
340+
341+ Text (
342+ text = item.title?.let { title ->
343+ setting.takeIf { it is PlatformSetting }?.let { i18n(title) } ? : title
344+ } ? : item.key ? : " No title" ,
345+
346+ style = MaterialTheme .typography.bodyLarge,
347+ modifier = Modifier .padding(start = 16 .dp)
348+ )
349+ }
350+ }
351+ }
352+ }
353+
354+ else -> {
355+ Text (
356+ style = MaterialTheme .typography.bodyLarge,
357+ color = Color .Red ,
358+ text = " Unsupported setting type!"
359+ )
360+ }
361+ }
362+ }
363+ }
364+ }
136365}
137366
138367private fun Setting.TriState.asToggleableState () = when (this ) {
0 commit comments