diff --git a/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt b/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt index 960f8dfe..5b175644 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt @@ -36,6 +36,7 @@ data class FieldsData( var dateDisplayFormat: String? = null, var hyperlinkUrl: String? = null, var displayText: String? = null, + var hasErrorInValue: Boolean = false, ) : Parcelable { companion object { diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AmountInputField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AmountInputField.kt index 27a70c63..abcfe9c4 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AmountInputField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/AmountInputField.kt @@ -3,25 +3,30 @@ package com.alfresco.content.process.ui.components import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R @Composable fun AmountInputField( textFieldValue: String? = null, onValueChanged: (String) -> Unit = { }, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Next, keyboardType = KeyboardType.Number, ) + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } + val leadingIcon: @Composable () -> Unit = when { !fieldsData.currency.isNullOrEmpty() -> { { @@ -36,8 +41,6 @@ fun AmountInputField( } } - val errorData = isValidInput(inputText = textFieldValue, fieldsData = fieldsData) - InputFieldWithLeading( modifier = Modifier.inputField(), maxLines = 1, @@ -46,8 +49,8 @@ fun AmountInputField( fieldsData = fieldsData, keyboardOptions = keyboardOptions, leadingIcon = leadingIcon, - isError = errorData.first, - errorMessage = errorData.second, + isError = isError, + errorMessage = errorMessage, ) } @@ -56,29 +59,3 @@ fun AmountInputField( fun AmountInputFieldPreview() { AmountInputField() } - -@Composable -fun isValidInput(inputText: String?, fieldsData: FieldsData): Pair { - val errorData = Pair(false, "") - - if (inputText.isNullOrEmpty()) { - return errorData - } - - if (inputText.toFloatOrNull() == null) { - return Pair(true, stringResource(R.string.error_invalid_format)) - } - - val minValue = fieldsData.minValue?.toFloat() ?: 0f - val maxValue = fieldsData.maxValue?.toFloat() ?: 0f - - if (inputText.toFloat() < minValue) { - return Pair(true, stringResource(R.string.error_min_value, minValue.toInt())) - } - - if (inputText.toFloat() > maxValue) { - return Pair(true, stringResource(R.string.error_max_value, maxValue.toInt())) - } - - return errorData -} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CheckboxField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CheckboxField.kt index 4b801a96..791e8a0a 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CheckboxField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/CheckboxField.kt @@ -36,11 +36,14 @@ import com.alfresco.content.process.ui.theme.AlfrescoError fun CheckBoxField( title: String = "", checkedValue: Boolean = false, - onCheckChanged: (Boolean) -> Unit = {}, + onCheckChanged: (Boolean) -> Unit = { }, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val context = LocalContext.current - var showError by remember { mutableStateOf(false) } + + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } val minimumLineLength = 2 // Change this to your desired value @@ -70,9 +73,6 @@ fun CheckBoxField( checked = checkedValue, onCheckedChange = { isChecked -> onCheckChanged(isChecked) - if (fieldsData.required) { - showError = !isChecked - } }, ) ClickableText( @@ -117,9 +117,9 @@ fun CheckBoxField( ) } - if (showError) { + if (isError) { Text( - text = stringResource(R.string.error_required_field), + text = errorMessage, color = AlfrescoError, modifier = Modifier .padding(horizontal = 16.dp, vertical = 0.dp), // Adjust padding as needed diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DateTimeField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DateTimeField.kt index 51e02f2c..7c223605 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DateTimeField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DateTimeField.kt @@ -5,35 +5,32 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import com.alfresco.content.DATE_FORMAT_4 import com.alfresco.content.component.DatePickerBuilder import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R @Composable fun DateTimeField( dateTimeValue: String = "", - onValueChanged: (String) -> Unit = { }, + onValueChanged: (String) -> Unit = {}, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Next, keyboardType = KeyboardType.Text, ) - val isError = dateTimeValue.isNotEmpty() && dateTimeValue.length < fieldsData.minLength - - val errorMessage = if (isError) { - stringResource(R.string.error_required_field, fieldsData.minLength) - } else { - "" - } + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } val context = LocalContext.current InputField( diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DropdownField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DropdownField.kt index 5086ecac..022ebba4 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DropdownField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/DropdownField.kt @@ -5,16 +5,17 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import com.alfresco.content.component.ComponentBuilder import com.alfresco.content.component.ComponentData import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R @Composable fun DropdownField( @@ -22,22 +23,18 @@ fun DropdownField( queryText: String = "", onValueChanged: (Pair) -> Unit = { }, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Next, keyboardType = KeyboardType.Text, ) - val isError = nameText.isNotEmpty() && nameText.length < fieldsData.minLength - - val errorMessage = if (isError) { - stringResource(R.string.error_required_field, fieldsData.minLength) - } else { - "" - } - val context = LocalContext.current + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } + InputField( colors = OutlinedTextFieldDefaults.colors( disabledBorderColor = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/IntegerInputField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/IntegerInputField.kt index 6663b6df..9783d206 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/IntegerInputField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/IntegerInputField.kt @@ -2,42 +2,30 @@ package com.alfresco.content.process.ui.components import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R @Composable fun IntegerInputField( textFieldValue: String? = null, onValueChanged: (String) -> Unit = { }, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Next, keyboardType = KeyboardType.Number, ) - var isError = false - var errorMessage = "" - - if (!textFieldValue.isNullOrEmpty()) { - val minValue = fieldsData.minValue?.toLong() ?: 0 - val maxValue = fieldsData.maxValue?.toLong() ?: 0 - - if (textFieldValue.toLong() < minValue) { - isError = true - errorMessage = stringResource(R.string.error_min_value, minValue) - } - - if (textFieldValue.toLong() > maxValue) { - isError = true - errorMessage = stringResource(R.string.error_max_value, maxValue) - } - } + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } InputField( modifier = Modifier.inputField(), diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/MultiLineInputField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/MultiLineInputField.kt index f22c13ce..7ad226ae 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/MultiLineInputField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/MultiLineInputField.kt @@ -2,30 +2,27 @@ package com.alfresco.content.process.ui.components import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R @Composable fun MultiLineInputField( textFieldValue: String? = null, onValueChanged: (String) -> Unit = { }, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Text, ) - val isError = !textFieldValue.isNullOrEmpty() && textFieldValue.length < fieldsData.minLength - - val errorMessage = if (isError) { - stringResource(R.string.error_min_length, fieldsData.minLength) - } else { - "" - } + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } InputField( modifier = Modifier.inputField(), diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/PeopleField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/PeopleField.kt index c0d4ba34..873289f5 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/PeopleField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/PeopleField.kt @@ -30,10 +30,12 @@ import com.alfresco.content.process.ui.theme.AlfrescoError @Composable fun PeopleField( userDetail: UserGroupDetails? = null, - onAssigneeSelected: (UserGroupDetails?) -> Unit = { }, + onAssigneeSelected: (UserGroupDetails?, Boolean) -> Unit = { _: UserGroupDetails?, _: Boolean -> }, fieldsData: FieldsData = FieldsData(), processEntry: ProcessEntry = ProcessEntry(), ) { + val isError = (fieldsData.required && userDetail == null) + val labelWithAsterisk = buildAnnotatedString { append(fieldsData.name) if (fieldsData.required) { @@ -63,10 +65,10 @@ fun PeopleField( IconButton(onClick = { SearchUserGroupComponentBuilder(context, processEntry) .onApply { userDetails -> - onAssigneeSelected(userDetails) + onAssigneeSelected(userDetails, isError) } .onCancel { - onAssigneeSelected(null) + onAssigneeSelected(null, isError) } .show() }) { diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SingleLineInputField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SingleLineInputField.kt index efce96bc..51f395ae 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SingleLineInputField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/SingleLineInputField.kt @@ -2,33 +2,30 @@ package com.alfresco.content.process.ui.components import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import com.alfresco.content.data.payloads.FieldsData -import com.alfresco.content.process.R @Composable fun SingleLineInputField( textFieldValue: String? = null, onValueChanged: (String) -> Unit = { }, fieldsData: FieldsData = FieldsData(), + errorData: Pair = Pair(false, ""), ) { val keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Next, keyboardType = KeyboardType.Text, ) - val isError = !textFieldValue.isNullOrEmpty() && textFieldValue.length < fieldsData.minLength - - val errorMessage = if (isError) { - stringResource(R.string.error_min_length, fieldsData.minLength) - } else { - "" - } + val isError by remember { mutableStateOf(errorData.first) } + val errorMessage by remember { mutableStateOf(errorData.second) } InputField( modifier = Modifier diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TrailingInputField.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TrailingInputField.kt index 3a759176..869c6bbd 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TrailingInputField.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/TrailingInputField.kt @@ -31,6 +31,7 @@ fun TrailingInputField( tint = trailingIconColor(), ) } + FieldType.DROPDOWN.value(), FieldType.RADIO_BUTTONS.value() -> { Icon( imageVector = Icons.Default.KeyboardArrowDown, @@ -38,6 +39,7 @@ fun TrailingInputField( tint = trailingIconColor(), ) } + else -> { if (focusState && !textValue.isNullOrEmpty()) { if (isError) { diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Utils.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Utils.kt index d990758f..87149929 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Utils.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/components/Utils.kt @@ -1,11 +1,15 @@ package com.alfresco.content.process.ui.components +import android.annotation.SuppressLint +import android.content.Context import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.alfresco.content.data.payloads.FieldsData +import com.alfresco.content.process.R @Composable fun trailingIconColor() = MaterialTheme.colorScheme.onSurfaceVariant @@ -14,3 +18,118 @@ fun Modifier.inputField() = this .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, top = 12.dp) // Add padding or other modifiers as needed + +fun integerInputError(value: String?, fieldsData: FieldsData, context: Context): Pair { + if (!value.isNullOrEmpty()) { + val minValue = fieldsData.minValue?.toLong() ?: 0 + val maxValue = fieldsData.maxValue?.toLong() ?: 0 + + if (value.toLong() < minValue) { + return Pair(true, context.getString(R.string.error_min_value, minValue)) + } + + if (value.toLong() > maxValue) { + return Pair(true, context.getString(R.string.error_max_value, maxValue)) + } + } + return Pair(false, "") +} + +fun singleLineInputError(value: String?, fieldsData: FieldsData, context: Context): Pair { + var isError = false + if (!value.isNullOrEmpty()) { + isError = (value.length < fieldsData.minLength) + } + + val errorMessage = if (isError) { + context.getString(R.string.error_min_length, fieldsData.minLength) + } else { + "" + } + return Pair(isError, errorMessage) +} + +fun multiLineInputError(value: String?, fieldsData: FieldsData, context: Context): Pair { + var isError = false + if (!value.isNullOrEmpty()) { + isError = (value.length < fieldsData.minLength) + } + + val errorMessage = if (isError) { + context.getString(R.string.error_min_length, fieldsData.minLength) + } else { + "" + } + return Pair(isError, errorMessage) +} + +fun booleanInputError(value: Boolean, fieldsData: FieldsData, context: Context): Pair { + var isError = false + if (fieldsData.required) { + isError = !value + } + + val errorMessage = if (isError) { + context.getString(R.string.error_required_field) + } else { + "" + } + return Pair(isError, errorMessage) +} + +fun amountInputError(value: String?, fieldsData: FieldsData, context: Context): Pair { + val errorData = Pair(false, "") + + if (value.isNullOrEmpty()) { + return errorData + } + + if (value.toFloatOrNull() == null) { + return Pair(true, context.getString(R.string.error_invalid_format)) + } + + val minValue = fieldsData.minValue?.toFloat() ?: 0f + val maxValue = fieldsData.maxValue?.toFloat() ?: 0f + + if (value.toFloat() < minValue) { + return Pair(true, context.getString(R.string.error_min_value, minValue.toInt())) + } + + if (value.toFloat() > maxValue) { + return Pair(true, context.getString(R.string.error_max_value, maxValue.toInt())) + } + + return errorData +} + +@SuppressLint("StringFormatInvalid") +fun dateTimeInputError(value: String?, fieldsData: FieldsData, context: Context): Pair { + var isError = false + + if (!value.isNullOrEmpty()) { + isError = (value.length < fieldsData.minLength) + } + + val errorMessage = if (isError) { + context.getString(R.string.error_required_field, fieldsData.minLength) + } else { + "" + } + return Pair(isError, errorMessage) +} + +@SuppressLint("StringFormatInvalid") +fun dropDownRadioInputError(value: String?, fieldsData: FieldsData, context: Context): Pair { + var isError = false + + if (!value.isNullOrEmpty()) { + isError = (value.length < fieldsData.minLength) + } + + val errorMessage = if (isError) { + context.getString(R.string.error_required_field, fieldsData.minLength) + } else { + "" + } + return Pair(isError, errorMessage) +} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt index 16db8f03..3499e7da 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScrollContent.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.navigation.NavController import com.alfresco.content.data.ProcessEntry @@ -23,88 +24,116 @@ import com.alfresco.content.process.ui.components.MultiLineInputField import com.alfresco.content.process.ui.components.PeopleField import com.alfresco.content.process.ui.components.ReadOnlyField import com.alfresco.content.process.ui.components.SingleLineInputField +import com.alfresco.content.process.ui.components.amountInputError +import com.alfresco.content.process.ui.components.booleanInputError +import com.alfresco.content.process.ui.components.dateTimeInputError +import com.alfresco.content.process.ui.components.dropDownRadioInputError +import com.alfresco.content.process.ui.components.integerInputError +import com.alfresco.content.process.ui.components.multiLineInputError +import com.alfresco.content.process.ui.components.singleLineInputError import com.alfresco.content.process.ui.fragments.FormViewModel import com.alfresco.content.process.ui.fragments.FormViewState @Composable fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormViewState, navController: NavController) { + val context = LocalContext.current when (field.type) { FieldType.TEXT.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + var errorData by remember { mutableStateOf(Pair(false, "")) } SingleLineInputField( textFieldValue = textFieldValue, onValueChanged = { newText -> textFieldValue = newText - viewModel.updateFieldValue(field.id, newText, state) + errorData = singleLineInputError(newText, field, context) + viewModel.updateFieldValue(field.id, newText, state, errorData.first) }, - field, + errorData = errorData, + fieldsData = field, + ) } FieldType.MULTI_LINE_TEXT.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + var errorData by remember { mutableStateOf(Pair(false, "")) } MultiLineInputField( textFieldValue = textFieldValue, onValueChanged = { newText -> textFieldValue = newText - viewModel.updateFieldValue(field.id, newText, state) + errorData = multiLineInputError(newText, field, context) + viewModel.updateFieldValue(field.id, newText, state, errorData.first) }, - field, + errorData = errorData, + fieldsData = field, ) } FieldType.INTEGER.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + var errorData by remember { mutableStateOf(Pair(false, "")) } IntegerInputField( textFieldValue = textFieldValue, onValueChanged = { newText -> textFieldValue = newText - viewModel.updateFieldValue(field.id, newText, state) + errorData = integerInputError(newText, field, context) + viewModel.updateFieldValue(field.id, newText, state, errorData.first) }, - field, + errorData = errorData, + fieldsData = field, ) } FieldType.AMOUNT.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + var errorData by remember { mutableStateOf(Pair(false, "")) } AmountInputField( textFieldValue = textFieldValue, onValueChanged = { newText -> textFieldValue = newText - viewModel.updateFieldValue(field.id, newText, state) + errorData = amountInputError(textFieldValue, field, context) + viewModel.updateFieldValue(field.id, newText, state, errorData.first) }, - field, + errorData = errorData, + fieldsData = field, ) } FieldType.BOOLEAN.value() -> { var checkedValue by remember { mutableStateOf(field.value as? Boolean ?: false) } + var errorData by remember { mutableStateOf(Pair(false, "")) } CheckBoxField( title = stringResource(id = R.string.title_workflow), checkedValue = checkedValue, onCheckChanged = { newChecked -> checkedValue = newChecked - viewModel.updateFieldValue(field.id, newChecked, state) + errorData = booleanInputError(newChecked, field, context) + viewModel.updateFieldValue(field.id, newChecked, state, errorData.first) }, - field, + errorData = errorData, + fieldsData = field, ) } FieldType.DATETIME.value(), FieldType.DATE.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + var errorData by remember { mutableStateOf(Pair(false, "")) } DateTimeField( dateTimeValue = textFieldValue, onValueChanged = { newText -> textFieldValue = newText - viewModel.updateFieldValue(field.id, newText, state) + errorData = dateTimeInputError(newText, field, context) + viewModel.updateFieldValue(field.id, newText, state, errorData.first) }, - field, + errorData = errorData, + fieldsData = field, ) } FieldType.DROPDOWN.value(), FieldType.RADIO_BUTTONS.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } var textFieldQuery by remember { mutableStateOf(field.options.find { it.name == textFieldValue }?.id ?: "") } + var errorData by remember { mutableStateOf(Pair(false, "")) } DropdownField( nameText = textFieldValue, @@ -112,8 +141,11 @@ fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormVi onValueChanged = { (newText, newQuery) -> textFieldValue = newText textFieldQuery = newQuery - viewModel.updateFieldValue(field.id, newText, state) + errorData = dropDownRadioInputError(newText, field, context) + viewModel.updateFieldValue(field.id, newText, state, errorData.first) }, + + errorData = errorData, fieldsData = field, ) } @@ -130,9 +162,9 @@ fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormVi var userDetailValue by remember { mutableStateOf(field.value as? UserGroupDetails) } PeopleField( userDetail = userDetailValue, - onAssigneeSelected = { userDetails -> + onAssigneeSelected = { userDetails, hasError -> userDetailValue = userDetails - viewModel.updateFieldValue(field.id, userDetails, state) + viewModel.updateFieldValue(field.id, userDetails, state, hasError) }, fieldsData = field, processEntry = ProcessEntry.withProcess(state.parent, field.type), diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt index 8fd05900..10ccc1a9 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt @@ -124,7 +124,7 @@ class FormViewModel( } } - fun updateFieldValue(fieldId: String, newValue: Any?, state: FormViewState) { + fun updateFieldValue(fieldId: String, newValue: Any?, state: FormViewState, hasError: Boolean) { val updatedFieldList = state.formFields.map { field -> if (field.id == fieldId) { var updatedValue = newValue @@ -145,7 +145,7 @@ class FormViewModel( updatedValue = null } } - field.copy(value = updatedValue) + field.copy(value = updatedValue, hasErrorInValue = hasError) } else { field } @@ -181,6 +181,7 @@ class FormViewModel( it.value != null -> { values[it.id] = repository.getUserOrGroup(it.value as? UserGroupDetails) } + else -> { values[it.id] = null } @@ -210,13 +211,17 @@ class FormViewModel( } private fun enableDisableActions(state: FormViewState) { - val hasAllRequiredData = hasFieldRequiredData(state) + val hasAllRequiredData = hasFieldValidData(state) setState { state.copy(enabledOutcomes = hasAllRequiredData) } } - private fun hasFieldRequiredData(state: FormViewState): Boolean { - return !state.formFields.filter { it.required }.any { it.value == null } + private fun hasFieldValidData(state: FormViewState): Boolean { + val hasValidDataInRequiredFields = !state.formFields.filter { it.required }.any { (it.value == null || it.hasErrorInValue) } + val hasValidDataInOtherFields = !state.formFields.filter { !it.required }.any { it.hasErrorInValue } + println("Test 1 == $hasValidDataInRequiredFields || $hasValidDataInOtherFields") + + return (hasValidDataInRequiredFields && hasValidDataInOtherFields) } companion object : MavericksViewModelFactory { diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt index 33d2dfca..a279cb48 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachedFilesFragment.kt @@ -80,8 +80,9 @@ class ProcessAttachedFilesFragment : BaseDetailFragment(), MavericksView, EntryL val fields = state.formFields.find { it.type == FieldType.UPLOAD.value() }!! handler.post { + val isError = (fields.required && state.listContents.isEmpty()) if (state.listContents.isNotEmpty()) { - viewModel.updateFieldValue(fields.id, state.listContents, state) + viewModel.updateFieldValue(fields.id, state.listContents, state, isError) binding.tvNoOfAttachments.visibility = View.VISIBLE binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) } else {