From f0b466071937a6895316d2112f65057045266855 Mon Sep 17 00:00:00 2001 From: toadlyBroodle Date: Fri, 17 Mar 2023 16:48:26 +0900 Subject: [PATCH] validate user input for all poll fields, add user's pubkey to recipients field, add poll field saveable texts to pollViewModel, --- .../amethyst/ui/actions/NewPollView.kt | 15 +++-- .../amethyst/ui/actions/NewPollViewModel.kt | 14 +++- .../amethyst/ui/components/PollClosing.kt | 27 +++++++- .../ui/components/PollConsensusThreshold.kt | 27 +++++++- .../amethyst/ui/components/PollOption.kt | 19 +++++- .../ui/components/PollPrimaryDescription.kt | 30 +++++---- .../ui/components/PollRecipientsField.kt | 13 ++-- .../ui/components/PollVoteValueRange.kt | 67 ++++++++++++++++--- app/src/main/res/values/strings.xml | 2 +- 9 files changed, 169 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollView.kt index 6e523eaf1..07067a2d5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollView.kt @@ -39,6 +39,11 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n val scrollState = rememberScrollState() + // if no recipients, add user's pubkey + if (pollViewModel.zapRecipients.isEmpty()) { + pollViewModel.zapRecipients.add(account.userProfile().pubkeyHex) + } + LaunchedEffect(Unit) { pollViewModel.load(account, baseReplyTo, quote) delay(100) @@ -109,7 +114,8 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } Text(stringResource(R.string.poll_heading_required)) - PollPrimaryDescription(pollViewModel = pollViewModel) + PollRecipientsField(pollViewModel) + PollPrimaryDescription(pollViewModel) pollViewModel.pollOptions.forEachIndexed { index, element -> PollOption(pollViewModel, index) } @@ -127,10 +133,9 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n ) } Text(stringResource(R.string.poll_heading_optional)) - PollRecipientsField() - PollVoteValueRange() - PollConsensusThreshold() - PollClosing() + PollVoteValueRange(pollViewModel) + PollConsensusThreshold(pollViewModel) + PollClosing(pollViewModel) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollViewModel.kt index 4027994fa..c90562fc9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollViewModel.kt @@ -3,12 +3,18 @@ package com.vitorpamplona.amethyst.ui.actions import androidx.compose.runtime.mutableStateListOf import androidx.compose.ui.text.input.TextFieldValue import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User class NewPollViewModel : NewPostViewModel() { + var zapRecipients = mutableStateListOf() var pollOptions = mutableStateListOf("", "") + var zapMax: Int? = null + var zapMin: Int? = null + var consensus: Int? = null + var closedAfter: Int? = null override fun load(account: Account, replyingTo: Note?, quote: Note?) { super.load(account, replyingTo, quote) @@ -32,13 +38,15 @@ class NewPollViewModel : NewPostViewModel() { override fun sendPost() { super.sendPost() + + // delete existing pollOptions + pollOptions = mutableStateListOf("", "") } override fun cancel() { - // delete existing pollOptions - pollOptions = mutableStateListOf("", "") - super.cancel() + + pollOptions = mutableStateListOf("", "") } override fun findUrlInMessage(): String? { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollClosing.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollClosing.kt index 2694e0ff2..c3dd9251c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollClosing.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollClosing.kt @@ -8,22 +8,44 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel @Composable -fun PollClosing() { +fun PollClosing(pollViewModel: NewPollViewModel) { var text by rememberSaveable { mutableStateOf("") } + var isInputValid = true + if (text.isNotEmpty()) { + try { + val int = text.toInt() + if (int < 0) { + isInputValid = false + } else { pollViewModel.closedAfter = int } + } catch (e: Exception) { isInputValid = false } + } + + val colorInValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.error, + unfocusedBorderColor = Color.Red + ) + val colorValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center @@ -33,6 +55,7 @@ fun PollClosing() { onValueChange = { text = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.width(150.dp), + colors = if (isInputValid) colorValid else colorInValid, label = { Text( text = stringResource(R.string.poll_closing_time), @@ -52,5 +75,5 @@ fun PollClosing() { @Preview @Composable fun PollClosingPreview() { - PollClosing() + PollClosing(NewPollViewModel()) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollConsensusThreshold.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollConsensusThreshold.kt index 67296bd94..2920d00b9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollConsensusThreshold.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollConsensusThreshold.kt @@ -8,22 +8,44 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel @Composable -fun PollConsensusThreshold() { +fun PollConsensusThreshold(pollViewModel: NewPollViewModel) { var text by rememberSaveable { mutableStateOf("") } + var isInputValid = true + if (text.isNotEmpty()) { + try { + val int = text.toInt() + if (int < 0 || int > 100) { + isInputValid = false + } else { pollViewModel.consensus = int } + } catch (e: Exception) { isInputValid = false } + } + + val colorInValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.error, + unfocusedBorderColor = Color.Red + ) + val colorValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center @@ -33,6 +55,7 @@ fun PollConsensusThreshold() { onValueChange = { text = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.width(150.dp), + colors = if (isInputValid) colorValid else colorInValid, label = { Text( text = stringResource(R.string.poll_consensus_threshold), @@ -52,5 +75,5 @@ fun PollConsensusThreshold() { @Preview @Composable fun PollConsensusThresholdPreview() { - PollConsensusThreshold() + PollConsensusThreshold(NewPollViewModel()) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollOption.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollOption.kt index c6132114a..df9533e9a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollOption.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollOption.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -15,6 +16,20 @@ import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel @Composable fun PollOption(pollViewModel: NewPollViewModel, optionIndex: Int) { + var isInputValid = true + if (pollViewModel.pollOptions[optionIndex].isEmpty()) { + isInputValid = false + } + + val colorInValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.error, + unfocusedBorderColor = Color.Red + ) + val colorValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + Row { OutlinedTextField( modifier = Modifier @@ -32,8 +47,8 @@ fun PollOption(pollViewModel: NewPollViewModel, optionIndex: Int) { text = stringResource(R.string.poll_option_description), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) - } - + }, + colors = if (isInputValid) colorValid else colorInValid ) if (optionIndex > 1) { Button( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollPrimaryDescription.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollPrimaryDescription.kt index 6f65c8266..e68896f40 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollPrimaryDescription.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollPrimaryDescription.kt @@ -1,13 +1,10 @@ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.foundation.border import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -30,6 +27,20 @@ fun PollPrimaryDescription(pollViewModel: NewPollViewModel) { val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current + var isInputValid = true + if (pollViewModel.message.text.isEmpty()) { + isInputValid = false + } + + val colorInValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.error, + unfocusedBorderColor = Color.Red + ) + val colorValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + OutlinedTextField( value = pollViewModel.message, onValueChange = { @@ -47,11 +58,6 @@ fun PollPrimaryDescription(pollViewModel: NewPollViewModel) { modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) - .border( - width = 1.dp, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - shape = RoundedCornerShape(8.dp) - ) .focusRequester(focusRequester) .onFocusChanged { if (it.isFocused) { @@ -64,11 +70,7 @@ fun PollPrimaryDescription(pollViewModel: NewPollViewModel) { color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, - colors = TextFieldDefaults - .outlinedTextFieldColors( - unfocusedBorderColor = Color.Transparent, - focusedBorderColor = Color.Transparent - ), + colors = if (isInputValid) colorValid else colorInValid, visualTransformation = UrlUserTagTransformation(MaterialTheme.colors.primary), textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content) ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollRecipientsField.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollRecipientsField.kt index 6cf29d4a0..131821727 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollRecipientsField.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollRecipientsField.kt @@ -5,23 +5,20 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel @Composable -fun PollRecipientsField() { - var text by rememberSaveable() { mutableStateOf("") } +fun PollRecipientsField(pollViewModel: NewPollViewModel) { OutlinedTextField( modifier = Modifier .fillMaxWidth(), - value = text, - onValueChange = { text = it }, + value = pollViewModel.zapRecipients[0], + onValueChange = { /* TODO */ }, + enabled = false, // TODO enable add recipients label = { Text( text = stringResource(R.string.poll_zap_recipients), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollVoteValueRange.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollVoteValueRange.kt index 63d8881eb..9e9160256 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollVoteValueRange.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/PollVoteValueRange.kt @@ -8,32 +8,82 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel @Composable -fun PollVoteValueRange() { - var minText by rememberSaveable { mutableStateOf("") } - var maxText by rememberSaveable { mutableStateOf("") } +fun PollVoteValueRange(pollViewModel: NewPollViewModel) { + var textMax by rememberSaveable { mutableStateOf("") } + var textMin by rememberSaveable { mutableStateOf("") } + + // check for zapMax amounts < 1 + var isMaxValid = true + if (textMax.isNotEmpty()) { + try { + val int = textMax.toInt() + if ( int < 1) + isMaxValid = false + else pollViewModel.zapMax = int + } catch (e: Exception) { isMaxValid = false } + } + + // check for minZap amounts < 1 + var isMinValid = true + if (textMin.isNotEmpty()) { + try { + val int = textMin.toInt() + if ( int < 1) + isMinValid = false + else pollViewModel.zapMin = int + } catch (e: Exception) { isMinValid = false } + } + + // check for zapMin > zapMax + if (textMin.isNotEmpty() && textMax.isNotEmpty()) { + try { + val intMin = textMin.toInt() + val intMax = textMax.toInt() + + if ( intMin > intMax) { + isMinValid = false + isMaxValid = false + } + } catch (e: Exception) { + isMinValid = false + isMaxValid = false + } + } + + val colorInValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.error, + unfocusedBorderColor = Color.Red) + val colorValid = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { OutlinedTextField( - value = minText, - onValueChange = { minText = it }, + value = textMin, + onValueChange = { textMin = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.width(150.dp), + colors = if (isMinValid) colorValid else colorInValid, label = { Text( text = stringResource(R.string.poll_zap_value_min), @@ -48,10 +98,11 @@ fun PollVoteValueRange() { } ) OutlinedTextField( - value = maxText, - onValueChange = { maxText = it }, + value = textMax, + onValueChange = { textMax = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.width(150.dp), + colors = if (isMaxValid) colorValid else colorInValid, label = { Text( text = stringResource(R.string.poll_zap_value_max), @@ -71,5 +122,5 @@ fun PollVoteValueRange() { @Preview @Composable fun PollVoteValueRangePreview() { - PollVoteValueRange() + PollVoteValueRange(NewPollViewModel()) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bba5e2191..2a5c1bdfd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -230,7 +230,7 @@ Zap minimum Zap maximum Consensus - % + (0–100)% sats Close after days