Skip to content

Commit

Permalink
fix: [ANDROAPP-6305] Take into account invalid date formats (#270)
Browse files Browse the repository at this point in the history
* fix: [ANDROAPP-6305] Take into account invalid date formats

* fix: [ANDROAPP-6305] ktlint
  • Loading branch information
xavimolloy authored Jul 5, 2024
1 parent 7c0b2bd commit 5d10a57
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import org.hisp.dhis.mobile.ui.designsystem.component.AgeInputType.None
import org.hisp.dhis.mobile.ui.designsystem.component.TimeUnitValues.YEARS
import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateTransformation.Companion.DATE_MASK
import org.hisp.dhis.mobile.ui.designsystem.component.internal.RegExValidations
import org.hisp.dhis.mobile.ui.designsystem.component.internal.dateIsInRange
import org.hisp.dhis.mobile.ui.designsystem.component.internal.isValidDate
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
Expand Down Expand Up @@ -321,19 +323,29 @@ private fun provideSupportingText(
): List<SupportingTextData>? =
(uiModel.inputType as? DateOfBirth)?.value?.text?.let {
if (
it.length == DATE_FORMAT.length &&
!dateIsInRange(parseStringDateToMillis(it), selectableDates)
it.length == DATE_FORMAT.length && (!isValidDate(it) || !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())
val supportingTextErrorList: MutableList<SupportingTextData> = mutableListOf()
if (!isValidDate(it)) {
val incorrectFormatText = provideStringResource("incorrect_date_format")
supportingTextErrorList.add(
SupportingTextData(
text = incorrectFormatText,
SupportingTextState.ERROR,
),
)
} else if (!dateIsInRange(parseStringDateToMillis(it), selectableDates)) {
val dateOutOfRangeText = "${provideStringResource("date_out_of_range")} (" +
formatStringToDate(selectableDates.initialDate) + " - " +
formatStringToDate(selectableDates.endDate) + ")"
supportingTextErrorList.add(
SupportingTextData(
text = dateOutOfRangeText,
SupportingTextState.ERROR,
),
)
}
supportingTextErrorList.plus(uiModel.supportingText ?: listOf()).toList()
} else {
uiModel.supportingText
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ import androidx.compose.ui.window.DialogProperties
import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateTimeVisualTransformation
import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateTransformation
import org.hisp.dhis.mobile.ui.designsystem.component.internal.RegExValidations
import org.hisp.dhis.mobile.ui.designsystem.component.internal.dateIsInRange
import org.hisp.dhis.mobile.ui.designsystem.component.internal.isValidDate
import org.hisp.dhis.mobile.ui.designsystem.component.internal.isValidHourFormat
import org.hisp.dhis.mobile.ui.designsystem.component.internal.yearIsInRange
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
Expand Down Expand Up @@ -90,6 +94,7 @@ fun InputDateTime(
var showDatePicker by rememberSaveable { mutableStateOf(false) }
var showTimePicker by rememberSaveable { mutableStateOf(false) }
var dateOutOfRangeText = uiModel.outOfRangeText ?: provideStringResource("date_out_of_range")

dateOutOfRangeText = "$dateOutOfRangeText (" + formatStringToDate(
uiModel.selectableDates.initialDate,
) + " - " +
Expand All @@ -99,18 +104,22 @@ fun InputDateTime(
text = incorrectHourFormatText,
SupportingTextState.ERROR,
)
val incorrectDateFormatItem = SupportingTextData(
text = provideStringResource("incorrect_date_format"),
SupportingTextState.ERROR,
)
val dateOutOfRangeItem = SupportingTextData(
text = dateOutOfRangeText,
SupportingTextState.ERROR,
)
val supportingTextList =
getSupportingTextList(uiModel, dateOutOfRangeItem, incorrectHourFormatItem)
getSupportingTextList(uiModel, dateOutOfRangeItem, incorrectHourFormatItem, incorrectDateFormatItem)

InputShell(
modifier = modifier.testTag("INPUT_DATE_TIME")
.focusRequester(focusRequester),
title = uiModel.title,
state = if (supportingTextList.contains(dateOutOfRangeItem)) InputShellState.ERROR else uiModel.state,
state = if (supportingTextList.contains(dateOutOfRangeItem) || supportingTextList.contains(incorrectDateFormatItem)) InputShellState.ERROR else uiModel.state,
isRequiredField = uiModel.isRequired,
onFocusChanged = uiModel.onFocusChanged,
inputField = {
Expand Down Expand Up @@ -228,7 +237,7 @@ fun InputDateTime(
SupportingText(
item.text,
item.state,
modifier = Modifier.testTag("INPUT_DATE_TIME_SUPPORTING_TEXT"),
modifier = Modifier.testTag("INPUT_DATE_TIME_SUPPORTING_TEXT" + item.text),
)
}
},
Expand Down Expand Up @@ -373,7 +382,7 @@ fun InputDateTime(
}
}

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

uiModel.supportingText?.forEach { item ->
Expand All @@ -383,6 +392,8 @@ fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: Suppo
val dateIsInRange: Boolean
val dateIsInYearRange: Boolean
val isValidHourFormat: Boolean
val isValidDateFormat: Boolean

when (uiModel.actionType) {
DateTimeActionType.TIME -> {
if (uiModel.inputTextFieldValue?.text!!.length == 4) {
Expand All @@ -401,15 +412,19 @@ fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: Suppo
)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text, getDefaultFormat(uiModel.actionType), uiModel.yearRange)
isValidHourFormat = isValidHourFormat(uiModel.inputTextFieldValue.text.substring(8, 12))
isValidDateFormat = isValidDate(uiModel.inputTextFieldValue.text.substring(0, 8))
if (!dateIsInRange || !dateIsInYearRange) supportingTextList.add(dateOutOfRangeItem)
if (!isValidDateFormat) supportingTextList.add(incorrectDateFormatItem)
if (!isValidHourFormat) supportingTextList.add(incorrectHourFormatItem)
}
}
DateTimeActionType.DATE -> {
if (uiModel.inputTextFieldValue?.text!!.length == 8) {
dateIsInRange = dateIsInRange(parseStringDateToMillis(uiModel.inputTextFieldValue.text), uiModel.selectableDates, uiModel.format)
isValidDateFormat = isValidDate(uiModel.inputTextFieldValue.text)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text, getDefaultFormat(uiModel.actionType), uiModel.yearRange)
if (!dateIsInRange || !dateIsInYearRange) supportingTextList.add(dateOutOfRangeItem)
if (!isValidDateFormat) supportingTextList.add(incorrectDateFormatItem)
}
}
}
Expand Down Expand Up @@ -564,14 +579,6 @@ fun formatStringToDate(dateString: String): String {
}
}

private fun isValidHourFormat(timeString: String): Boolean {
val hourRange = IntRange(0, 24)
val minuteRange = IntRange(0, 60)

return timeString.length == 4 && hourRange.contains(timeString.substring(0, 2).toInt()) &&
minuteRange.contains(timeString.substring(2, 4).toInt())
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun timePickerColors(): TimePickerColors {
Expand All @@ -592,21 +599,6 @@ private fun timePickerColors(): TimePickerColors {
)
}

internal fun dateIsInRange(date: Long, allowedDates: SelectableDates, format: String = "ddMMyyyy"): Boolean {
return (
date >= parseStringDateToMillis(allowedDates.initialDate, format) &&
date <= parseStringDateToMillis(allowedDates.endDate, format)
)
}

fun yearIsInRange(date: String, pattern: String, yearRange: IntRange): Boolean {
val cal = Calendar.getInstance()
return date.parseDate(pattern)?.let {
cal.time = it
yearRange.contains(cal.get(Calendar.YEAR))
} ?: false
}

fun String.parseDate(pattern: String): Date? {
return if (isNotEmpty() && length == pattern.length) {
val sdf = SimpleDateFormat(pattern, Locale.getDefault())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.hisp.dhis.mobile.ui.designsystem.component.internal

import org.hisp.dhis.mobile.ui.designsystem.component.SelectableDates
import org.hisp.dhis.mobile.ui.designsystem.component.parseDate
import org.hisp.dhis.mobile.ui.designsystem.component.parseStringDateToMillis
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar

internal fun dateIsInRange(date: Long, allowedDates: SelectableDates, format: String = "ddMMyyyy"): Boolean {
return (
date >= parseStringDateToMillis(allowedDates.initialDate, format) &&
date <= parseStringDateToMillis(allowedDates.endDate, format)
)
}

internal fun yearIsInRange(date: String, pattern: String, yearRange: IntRange): Boolean {
val cal = Calendar.getInstance()
return date.parseDate(pattern)?.let {
cal.time = it
yearRange.contains(cal.get(Calendar.YEAR))
} ?: false
}

internal fun isValidHourFormat(timeString: String): Boolean {
val hourRange = IntRange(0, 24)
val minuteRange = IntRange(0, 60)

return timeString.length == 4 && hourRange.contains(timeString.substring(0, 2).toInt()) &&
minuteRange.contains(timeString.substring(2, 4).toInt())
}

internal fun isValidDate(text: String): Boolean {
if (text.length != 8) return false
val format = SimpleDateFormat("ddMMyyyy")
format.isLenient = false
return try {
format.parse(text)
true
} catch (e: ParseException) {
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<string name="cancel">Cancel</string>
<string name="select_date">Select date</string>
<string name="date_out_of_range">Date out of range </string>
<string name="incorrect_date_format">Incorrect date format </string>
<string name="wrong_hour_format">Incorrect time format </string>
<string name="search_to_see_more">Not all options are displayed.\n Search to see more.</string>
</resources>

0 comments on commit 5d10a57

Please sign in to comment.