From 2777b856de18585e2fec6de31d43a64e5501a0b6 Mon Sep 17 00:00:00 2001 From: Amanpal Singh <87360222+aman-alfresco@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:38:40 +0530 Subject: [PATCH] ADST-261 (#323) * fixed crash * optimize error handling * add more sceanrio to error handling --- .../content/data/payloads/FieldsData.kt | 34 +++++ .../process/ui/components/AmountInputField.kt | 32 +---- .../process/ui/components/CheckboxField.kt | 11 +- .../process/ui/components/DateTimeField.kt | 18 +-- .../process/ui/components/DropdownField.kt | 16 +-- .../ui/components/IntegerInputField.kt | 26 +--- .../ui/components/MultiLineInputField.kt | 16 +-- .../process/ui/components/PeopleField.kt | 4 +- .../ui/components/SingleLineInputField.kt | 16 +-- .../ui/components/TrailingInputField.kt | 2 + .../content/process/ui/components/Utils.kt | 133 ++++++++++++++++++ .../ui/composeviews/FormDetailScreen.kt | 13 +- .../ui/composeviews/FormScrollContent.kt | 72 ++++++++-- .../process/ui/fragments/FormViewModel.kt | 47 ++++--- .../fragments/ProcessAttachedFilesFragment.kt | 3 +- 15 files changed, 292 insertions(+), 151 deletions(-) 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..131e0229 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 errorData: Pair = Pair(false, ""), ) : Parcelable { companion object { @@ -70,6 +71,39 @@ data class FieldsData( displayText = raw.displayText, ) } + + /** + * returns the FieldsData obj by using Fields obj + */ + fun withUpdateField(raw: FieldsData, value: Any?, errorData: Pair): FieldsData { + return FieldsData( + fieldType = raw.fieldType, + id = raw.id, + name = raw.name, + message = raw.message, + type = raw.type, + placeHolder = raw.placeHolder, + value = value, + minLength = raw.minLength, + maxLength = raw.maxLength, + minValue = raw.minValue, + maxValue = raw.maxValue, + regexPattern = raw.regexPattern, + required = raw.required, + readOnly = raw.readOnly, + overrideId = raw.overrideId, + params = raw.params, + currency = raw.currency, + enableFractions = raw.enableFractions, + enablePeriodSeparator = raw.enablePeriodSeparator, + options = raw.options, + fields = raw.fields, + dateDisplayFormat = raw.dateDisplayFormat, + hyperlinkUrl = raw.hyperlinkUrl, + displayText = raw.displayText, + errorData = errorData, + ) + } } } 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..51eff763 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,19 +3,19 @@ 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.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, @@ -36,8 +36,6 @@ fun AmountInputField( } } - val errorData = isValidInput(inputText = textFieldValue, fieldsData = fieldsData) - InputFieldWithLeading( modifier = Modifier.inputField(), maxLines = 1, @@ -56,29 +54,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..42f22cd5 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,11 @@ 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 minimumLineLength = 2 // Change this to your desired value @@ -70,9 +70,6 @@ fun CheckBoxField( checked = checkedValue, onCheckedChange = { isChecked -> onCheckChanged(isChecked) - if (fieldsData.required) { - showError = !isChecked - } }, ) ClickableText( @@ -117,9 +114,9 @@ fun CheckBoxField( ) } - if (showError) { + if (errorData.first) { Text( - text = stringResource(R.string.error_required_field), + text = errorData.second, 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..c0f78199 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,36 +5,28 @@ 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.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 context = LocalContext.current InputField( colors = OutlinedTextFieldDefaults.colors( @@ -65,8 +57,8 @@ fun DateTimeField( onValueChanged = onValueChanged, fieldsData = fieldsData, keyboardOptions = keyboardOptions, - isError = isError, - errorMessage = errorMessage, + isError = errorData.first, + errorMessage = errorData.second, isEnabled = false, ) } 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..336d86de 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,15 @@ 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.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,20 +21,13 @@ 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 InputField( @@ -69,8 +61,8 @@ fun DropdownField( textFieldValue = nameText, fieldsData = fieldsData, keyboardOptions = keyboardOptions, - isError = isError, - errorMessage = errorMessage, + isError = errorData.first, + errorMessage = errorData.second, isEnabled = false, ) } 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 01c9a1d1..ad155510 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,43 +2,25 @@ 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.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?.toInt() ?: 0 - val maxValue = fieldsData.maxValue?.toInt() ?: 0 - - if (textFieldValue.toInt() < minValue) { - isError = true - errorMessage = stringResource(R.string.error_min_value, minValue) - } - - if (textFieldValue.toInt() > maxValue) { - isError = true - errorMessage = stringResource(R.string.error_max_value, maxValue) - } - } - InputField( modifier = Modifier.inputField(), maxLines = 1, @@ -46,8 +28,8 @@ fun IntegerInputField( onValueChanged = onValueChanged, fieldsData = fieldsData, keyboardOptions = keyboardOptions, - isError = isError, - errorMessage = errorMessage, + isError = errorData.first, + errorMessage = errorData.second, ) } 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..a9b496e6 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,31 +2,23 @@ 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.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 { - "" - } - InputField( modifier = Modifier.inputField(), maxLines = 5, @@ -34,8 +26,8 @@ fun MultiLineInputField( onValueChanged = onValueChanged, fieldsData = fieldsData, keyboardOptions = keyboardOptions, - isError = isError, - errorMessage = errorMessage, + isError = errorData.first, + errorMessage = errorData.second, ) } 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..e5cef1f0 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 @@ -10,6 +10,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -30,9 +31,10 @@ import com.alfresco.content.process.ui.theme.AlfrescoError @Composable fun PeopleField( userDetail: UserGroupDetails? = null, - onAssigneeSelected: (UserGroupDetails?) -> Unit = { }, + onAssigneeSelected: (UserGroupDetails?) -> Unit = {}, fieldsData: FieldsData = FieldsData(), processEntry: ProcessEntry = ProcessEntry(), + errorData: Pair = Pair(false, ""), ) { val labelWithAsterisk = buildAnnotatedString { append(fieldsData.name) 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..4e7c2042 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,34 +2,26 @@ 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.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 { - "" - } - InputField( modifier = Modifier .inputField() @@ -39,8 +31,8 @@ fun SingleLineInputField( onValueChanged = onValueChanged, fieldsData = fieldsData, keyboardOptions = keyboardOptions, - isError = isError, - errorMessage = errorMessage, + isError = errorData.first, + errorMessage = errorData.second, ) } 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..f8694086 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,16 @@ 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.UserGroupDetails +import com.alfresco.content.data.payloads.FieldsData +import com.alfresco.content.process.R @Composable fun trailingIconColor() = MaterialTheme.colorScheme.onSurfaceVariant @@ -14,3 +19,131 @@ 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 { + var errorData = Pair(false, "") + + if (!value.isNullOrEmpty()) { + val minValue = fieldsData.minValue?.toLong() ?: 0 + val maxValue = fieldsData.maxValue?.toLong() ?: 0 + + if (value.toLong() < minValue) { + errorData = Pair(true, context.getString(R.string.error_min_value, minValue)) + } + + if (value.toLong() > maxValue) { + errorData = Pair(true, context.getString(R.string.error_max_value, maxValue)) + } + } + + println("IntegerInputField 3 == $errorData") + + return errorData +} + +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) +} + +fun userGroupInputError(value: UserGroupDetails?, fieldsData: FieldsData, context: Context): Pair { + val isError = (fieldsData.required && value == null) + + val errorMessage = "" + + return Pair(isError, errorMessage) +} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt index 2b7f8619..b0968783 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormDetailScreen.kt @@ -1,5 +1,6 @@ package com.alfresco.content.process.ui.composeviews +import android.annotation.SuppressLint import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -8,11 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -26,16 +23,12 @@ import com.alfresco.content.process.ui.components.Outcomes import com.alfresco.content.process.ui.fragments.FormViewModel import com.alfresco.content.process.ui.fragments.FormViewState -@OptIn(ExperimentalComposeUiApi::class) +@SuppressLint("MutableCollectionMutableState") @Composable fun FormDetailScreen(state: FormViewState, viewModel: FormViewModel, outcomes: List, navController: NavController) { val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current - val formList by remember(state.formFields) { - mutableStateOf(state.formFields.map { it }) - } - Column( modifier = Modifier .fillMaxWidth() @@ -56,7 +49,7 @@ fun FormDetailScreen(state: FormViewState, viewModel: FormViewModel, outcomes: L key = { it.id }, - items = formList, + items = state.formFields, ) { field -> FormScrollContent(field, viewModel, state, navController) } 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..5a745f36 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,123 @@ 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.components.userGroupInputError 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) }, - 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) }, - field, + errorData = errorData, + fieldsData = field, ) } FieldType.INTEGER.value() -> { var textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + var errorData by remember { mutableStateOf(field.errorData) } + 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) }, - 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) }, - 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) }, - 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) }, - 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,14 +148,18 @@ 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) }, + + errorData = errorData, fieldsData = field, ) } FieldType.READONLY_TEXT.value(), FieldType.READONLY.value() -> { val textFieldValue by remember { mutableStateOf(field.value as? String ?: "") } + ReadOnlyField( textFieldValue = textFieldValue, fieldsData = field, @@ -128,13 +168,17 @@ fun FormScrollContent(field: FieldsData, viewModel: FormViewModel, state: FormVi FieldType.PEOPLE.value(), FieldType.FUNCTIONAL_GROUP.value() -> { var userDetailValue by remember { mutableStateOf(field.value as? UserGroupDetails) } + var errorData by remember { mutableStateOf(Pair(false, "")) } + PeopleField( userDetail = userDetailValue, onAssigneeSelected = { userDetails -> userDetailValue = userDetails - viewModel.updateFieldValue(field.id, userDetails, state) + errorData = userGroupInputError(userDetails, field, context) + viewModel.updateFieldValue(field.id, userDetails, state, errorData) }, fieldsData = field, + errorData = errorData, 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 b3f76bf7..9e3df495 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 @@ -106,13 +106,18 @@ class FormViewModel( } is Success -> { + val fields = it().fields.flatMap { listData -> listData.fields } + val updatedState = copy( parent = processEntry, - formFields = it().fields.flatMap { listData -> listData.fields }, + formFields = fields, processOutcomes = it().outcomes, requestStartForm = Success(it()), ) - enableDisableActions(updatedState) + + val hasAllRequiredData = hasFieldValidData(fields) + updateStateData(hasAllRequiredData, fields) + updatedState } @@ -124,8 +129,10 @@ class FormViewModel( } } - fun updateFieldValue(fieldId: String, newValue: Any?, state: FormViewState) { - val updatedFieldList = state.formFields.map { field -> + fun updateFieldValue(fieldId: String, newValue: Any?, state: FormViewState, errorData: Pair) { + val updatedFieldList: MutableList = mutableListOf() + + state.formFields.forEach { field -> if (field.id == fieldId) { var updatedValue = newValue when { @@ -145,17 +152,15 @@ class FormViewModel( updatedValue = null } } - field.copy(value = updatedValue) + updatedFieldList.add(FieldsData.withUpdateField(field, updatedValue, errorData)) } else { - field + updatedFieldList.add(field) } } - val updatedState = state.copy( - formFields = updatedFieldList, - ) + val hasAllRequiredData = hasFieldValidData(updatedFieldList) - enableDisableActions(updatedState) + updateStateData(hasAllRequiredData, updatedFieldList) } fun startWorkflow() = withState { state -> @@ -177,7 +182,15 @@ class FormViewModel( fields.forEach { when (it.type) { FieldType.PEOPLE.value(), FieldType.FUNCTIONAL_GROUP.value() -> { - values[it.id] = repository.getUserOrGroup(it.value as UserGroupDetails) + when { + it.value != null -> { + values[it.id] = repository.getUserOrGroup(it.value as? UserGroupDetails) + } + + else -> { + values[it.id] = null + } + } } FieldType.DATETIME.value(), FieldType.DATE.value() -> { @@ -202,14 +215,14 @@ class FormViewModel( return values } - private fun enableDisableActions(state: FormViewState) { - val hasAllRequiredData = hasFieldRequiredData(state) - - setState { state.copy(enabledOutcomes = hasAllRequiredData) } + private fun updateStateData(enabledOutcomes: Boolean, fields: List) { + setState { copy(enabledOutcomes = enabledOutcomes, formFields = fields) } } - private fun hasFieldRequiredData(state: FormViewState): Boolean { - return !state.formFields.filter { it.required }.any { it.value == null } + private fun hasFieldValidData(fields: List): Boolean { + val hasValidDataInRequiredFields = !fields.filter { it.required }.any { (it.value == null || it.errorData.first) } + val hasValidDataInOtherFields = !fields.filter { !it.required }.any { it.errorData.first } + 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..a7dceb39 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, Pair(isError, "")) binding.tvNoOfAttachments.visibility = View.VISIBLE binding.tvNoOfAttachments.text = getString(R.string.text_multiple_attachment, state.listContents.size) } else {