Skip to content

Commit

Permalink
Merge pull request #274 from dhis2/update-develop-with-main
Browse files Browse the repository at this point in the history
chore: [ANDROAPP-6305], [ANDROAPP-6271] Update develop with main
  • Loading branch information
xavimolloy authored Jul 11, 2024
2 parents 8ef353a + 28efdf1 commit 53255f1
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 71 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 @@ -324,19 +326,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 @@ -18,6 +18,7 @@ import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerColors
import androidx.compose.material3.DatePickerDefaults
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.DatePickerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
Expand Down Expand Up @@ -53,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 @@ -89,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 @@ -98,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 @@ -227,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 All @@ -238,31 +248,7 @@ fun InputDateTime(
},
inputStyle = uiModel.inputStyle,
)
var datePickerState = rememberDatePickerState(
selectableDates = getSelectableDates(uiModel),
)
if (!uiModel.inputTextFieldValue?.text.isNullOrEmpty() && uiModel.actionType != DateTimeActionType.TIME) {
datePickerState = if (uiModel.actionType == DateTimeActionType.DATE_TIME && uiModel.inputTextFieldValue?.text?.length == 12 && yearIsInRange(uiModel.inputTextFieldValue.text.substring(0, 8), uiModel.yearRange)) {
rememberDatePickerState(
initialSelectedDateMillis = parseStringDateToMillis(
uiModel.inputTextFieldValue.text.substring(0, uiModel.inputTextFieldValue.text.length - 4),
pattern = uiModel.format,
),
yearRange = uiModel.yearRange,
selectableDates = getSelectableDates(uiModel),
)
} else {
if (uiModel.inputTextFieldValue?.text?.length == 8 && yearIsInRange(uiModel.inputTextFieldValue.text, uiModel.yearRange)) {
rememberDatePickerState(
initialSelectedDateMillis = parseStringDateToMillis(uiModel.inputTextFieldValue.text, uiModel.format),
yearRange = uiModel.yearRange,
selectableDates = getSelectableDates(uiModel),
)
} else {
datePickerState
}
}
}
val datePickerState = provideDatePickerState(uiModel)

if (showDatePicker) {
MaterialTheme(
Expand Down Expand Up @@ -402,7 +388,7 @@ fun getSelectableDates(uiModel: InputDateTimeModel): androidx.compose.material3.
}
}

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 @@ -412,6 +398,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 @@ -428,24 +416,54 @@ fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: Suppo
),
uiModel.selectableDates, uiModel.format,
)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text.substring(0, uiModel.inputTextFieldValue.text.length - 4), uiModel.yearRange)
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)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text, uiModel.yearRange)
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)
}
}
}
}
return supportingTextList.toList()
}

@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun provideDatePickerState(uiModel: InputDateTimeModel): DatePickerState {
return uiModel.inputTextFieldValue?.text?.takeIf {
it.isNotEmpty() &&
yearIsInRange(it, getDefaultFormat(uiModel.actionType), uiModel.yearRange)
}?.let {
rememberDatePickerState(
initialSelectedDateMillis = parseStringDateToMillis(
dateString = it,
pattern = getDefaultFormat(uiModel.actionType),
),
yearRange = uiModel.yearRange,
selectableDates = getSelectableDates(uiModel),
)
} ?: rememberDatePickerState(selectableDates = getSelectableDates(uiModel))
}

private fun getDefaultFormat(actionType: DateTimeActionType): String {
return when (actionType) {
DateTimeActionType.DATE -> "ddMMyyyy"
DateTimeActionType.TIME -> "HHmm"
DateTimeActionType.DATE_TIME -> "ddMMyyyyHHmm"
}
}

enum class DateTimeActionType {
DATE, TIME, DATE_TIME
}
Expand Down Expand Up @@ -547,16 +565,12 @@ private fun getTime(timePickerState: TimePickerState, format: String? = "HHmm"):
return formater.format(cal.time)
}

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)
sdf.timeZone = TimeZone.getTimeZone("UTC")
cal.time = sdf.parse(dateString) ?: Date()
fun parseStringDateToMillis(dateString: String, pattern: String = "ddMMyyyy"): Long {
val cal = Calendar.getInstance()
return dateString.parseDate(pattern)?.let {
cal.time = it
cal.timeInMillis
} else {
0L
}
} ?: 0L
}

data class SelectableDates(
Expand All @@ -572,14 +586,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 @@ -600,17 +606,14 @@ 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, yearRange: IntRange): Boolean {
return (
yearRange.contains(date.substring(date.length - 4, date.length).toInt())
)
fun String.parseDate(pattern: String): Date? {
return if (isNotEmpty() && length == pattern.length) {
val sdf = SimpleDateFormat(pattern, Locale.getDefault())
sdf.timeZone = TimeZone.getTimeZone("UTC")
sdf.parse(this)
} else {
null
}
}

@OptIn(ExperimentalMaterial3Api::class)
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">Cancelar</string>
<string name="select_date">Seleccionar fecha</string>
<string name="date_out_of_range">Fecha fuera de rango</string>
<string name="incorrect_date_format">Formato de fecha incorrecto</string>
<string name="wrong_hour_format">Formato de hora incorrecto</string>
<string name="search_to_see_more">No se muestran todas las opciones.\n Busca para ver más.</string>
</resources>
1 change: 1 addition & 0 deletions designsystem/src/commonMain/resources/values/strings.xml
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 53255f1

Please sign in to comment.