Skip to content

Commit

Permalink
fix: [ANDROAPP-6104] do not allow futures dates in age by default (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmmateos authored Jul 1, 2024
1 parent 6f352c0 commit d64dd57
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -71,12 +73,16 @@ fun InputAge(
is Age -> HelperStyle.WITH_HELPER_AFTER
}
}
val selectableDates = uiModel.selectableDates ?: SelectableDates(
MIN_DATE,
SimpleDateFormat(DATE_FORMAT).format(Calendar.getInstance().time),
)

val focusRequester = remember { FocusRequester() }
val datePickerState = rememberDatePickerState(
selectableDates = object : androidx.compose.material3.SelectableDates {
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
return dateIsInRange(utcTimeMillis, uiModel.selectableDates)
return dateIsInRange(utcTimeMillis, selectableDates)
}
},
)
Expand Down Expand Up @@ -118,10 +124,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) {
Expand Down Expand Up @@ -193,7 +204,7 @@ fun InputAge(
},
secondaryButton = calendarButton,
supportingText = {
uiModel.supportingText?.forEach { label ->
supportingText?.forEach { label ->
SupportingText(
label.text,
label.state,
Expand Down Expand Up @@ -309,6 +320,34 @@ private fun updateDateOfBirth(inputType: DateOfBirth, newText: TextFieldValue):
}
}

@Composable
private fun provideSupportingText(
uiModel: InputAgeModel,
selectableDates: SelectableDates,
): List<SupportingTextData>? =
(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

Expand Down Expand Up @@ -364,5 +403,5 @@ data class InputAgeModel(
val acceptText: String? = null,
val cancelText: String? = null,
val onValueChanged: (AgeInputType) -> Unit,
val selectableDates: SelectableDates = SelectableDates("10111901", "12112124"),
val selectableDates: SelectableDates? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ fun getSelectableDates(uiModel: InputDateTimeModel): androidx.compose.material3.
}
}

private fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: SupportingTextData, incorrectHourFormatItem: SupportingTextData): List<SupportingTextData> {
fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: SupportingTextData, incorrectHourFormatItem: SupportingTextData): List<SupportingTextData> {
val supportingTextList = mutableListOf<SupportingTextData>()

uiModel.supportingText?.forEach { item ->
Expand Down Expand Up @@ -547,7 +547,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)
Expand All @@ -564,7 +564,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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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>(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()
}
}

0 comments on commit d64dd57

Please sign in to comment.