diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt index f1582e287..4aefaff9b 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt @@ -39,6 +39,8 @@ import org.hisp.dhis.mobile.ui.designsystem.resource.provideStringResource import org.hisp.dhis.mobile.ui.designsystem.theme.DHIS2LightColorScheme import org.hisp.dhis.mobile.ui.designsystem.theme.Outline import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing +import java.text.SimpleDateFormat +import java.util.Calendar /** * DHIS2 Input Age component wraps DHIS2 [InputShell]. @@ -71,6 +73,7 @@ fun InputAge( is Age -> HelperStyle.WITH_HELPER_AFTER } } + val selectableDates = uiModel.selectableDates val focusRequester = remember { FocusRequester() } val datePickerState = rememberDatePickerState() @@ -112,10 +115,15 @@ fun InputAge( } } + val supportingText = provideSupportingText(uiModel, selectableDates) + InputShell( modifier = modifier.testTag("INPUT_AGE").focusRequester(focusRequester), title = uiModel.title, - state = uiModel.state, + state = when (supportingText) { + uiModel.supportingText -> uiModel.state + else -> InputShellState.ERROR + }, isRequiredField = uiModel.isRequired, inputField = { when (uiModel.inputType) { @@ -187,7 +195,7 @@ fun InputAge( }, secondaryButton = calendarButton, supportingText = { - uiModel.supportingText?.forEach { label -> + supportingText?.forEach { label -> SupportingText( label.text, label.state, @@ -306,6 +314,34 @@ private fun updateDateOfBirth(inputType: DateOfBirth, newText: TextFieldValue): } } +@Composable +private fun provideSupportingText( + uiModel: InputAgeModel, + selectableDates: SelectableDates, +): List? = + (uiModel.inputType as? DateOfBirth)?.value?.text?.let { + if ( + it.length == DATE_FORMAT.length && + !dateIsInRange(parseStringDateToMillis(it), selectableDates) + ) { + val dateOutOfRangeText = "${provideStringResource("date_out_of_range")} (" + + formatStringToDate(selectableDates.initialDate) + " - " + + formatStringToDate(selectableDates.endDate) + ")" + + listOf( + SupportingTextData( + text = dateOutOfRangeText, + SupportingTextState.ERROR, + ), + ).plus(uiModel.supportingText ?: listOf()) + } else { + uiModel.supportingText + } + } ?: uiModel.supportingText + +internal const val MIN_DATE = "10111901" +internal const val DATE_FORMAT = "ddMMYYYY" + sealed interface AgeInputType { data object None : AgeInputType @@ -361,5 +397,8 @@ data class InputAgeModel( val acceptText: String? = null, val cancelText: String? = null, val onValueChanged: (AgeInputType) -> Unit, - val selectableDates: SelectableDates = SelectableDates("10111901", "12112124"), + val selectableDates: SelectableDates = SelectableDates( + MIN_DATE, + SimpleDateFormat(DATE_FORMAT).format(Calendar.getInstance().time), + ), ) diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt index eb062b308..33822bd04 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt @@ -392,7 +392,7 @@ fun InputDateTime( } } -private fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: SupportingTextData, incorrectHourFormatItem: SupportingTextData): List { +fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: SupportingTextData, incorrectHourFormatItem: SupportingTextData): List { val supportingTextList = mutableListOf() uiModel.supportingText?.forEach { item -> @@ -537,7 +537,7 @@ private fun getTime(timePickerState: TimePickerState, format: String? = "HHmm"): return formater.format(cal.time) } -private fun parseStringDateToMillis(dateString: String, pattern: String = "ddMMyyyy", locale: Locale = Locale.getDefault()): Long { +fun parseStringDateToMillis(dateString: String, pattern: String = "ddMMyyyy", locale: Locale = Locale.getDefault()): Long { return if (dateString.isNotEmpty()) { val cal = Calendar.getInstance() val sdf = SimpleDateFormat(pattern, locale) @@ -554,7 +554,7 @@ data class SelectableDates( val endDate: String, ) -private fun formatStringToDate(dateString: String): String { +fun formatStringToDate(dateString: String): String { return if (dateString.length == 8) { dateString.substring(0, 2) + "/" + dateString.substring(2, 4) + "/" + dateString.substring(4, 8) } else { diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt index 3b327004c..fbffaf18c 100644 --- a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput import org.junit.Rule import org.junit.Test +import java.text.SimpleDateFormat +import java.util.Calendar class InputAgeTest { @@ -148,4 +150,30 @@ class InputAgeTest { rule.onNodeWithTag("INPUT_AGE_OPEN_CALENDAR_BUTTON").assertDoesNotExist() rule.onNodeWithTag("INPUT_AGE_TIME_UNIT_SELECTOR").assertDoesNotExist() } + + @Test + fun shouldShowErrorMessageWhenAgeIsOnFuture() { + val calendar = Calendar.getInstance().apply { + add(Calendar.DAY_OF_MONTH, 1) + } + val futureDate = SimpleDateFormat(DATE_FORMAT).format(calendar.time) + var inputType by mutableStateOf(AgeInputType.DateOfBirth.EMPTY) + + rule.setContent { + InputAge( + InputAgeModel( + title = "Label", + inputType = inputType, + onValueChanged = { + inputType = it + }, + ), + + ) + } + + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").performTextInput(futureDate) + + rule.onNodeWithTag("INPUT_AGE_SUPPORTING_TEXT").assertExists() + } }