From b8db43cd3d1fc0049cf99c25d93a92763f737a83 Mon Sep 17 00:00:00 2001 From: andresmr Date: Thu, 17 Oct 2024 17:07:51 +0200 Subject: [PATCH] update docs with develop --- .../common/screens/cards/ListCardScreen.kt | 1 - .../designsystem/component/InputDateTime.kt | 261 +++++++++++++++--- 2 files changed, 221 insertions(+), 41 deletions(-) diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/cards/ListCardScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/cards/ListCardScreen.kt index 75442ebee..eb5ffa5e3 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/cards/ListCardScreen.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/cards/ListCardScreen.kt @@ -42,7 +42,6 @@ import org.hisp.dhis.mobile.ui.designsystem.component.ListCardDescriptionModel import org.hisp.dhis.mobile.ui.designsystem.component.ListCardTitleModel import org.hisp.dhis.mobile.ui.designsystem.component.MetadataAvatarSize import org.hisp.dhis.mobile.ui.designsystem.component.SelectionState -import org.hisp.dhis.mobile.ui.designsystem.component.internal.ImageCardData import org.hisp.dhis.mobile.ui.designsystem.component.state.rememberAdditionalInfoColumnState import org.hisp.dhis.mobile.ui.designsystem.component.state.rememberListCardState import org.hisp.dhis.mobile.ui.designsystem.resource.provideDHIS2Icon 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 4d3bdf0c4..11f067271 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 @@ -26,6 +26,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TimePicker import androidx.compose.material3.TimePickerLayoutType import androidx.compose.material3.TimePickerState +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.material3.rememberTimePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -53,11 +55,16 @@ import androidx.compose.ui.window.DialogProperties import org.hisp.dhis.mobile.ui.designsystem.component.internal.convertStringToTextFieldValue import org.hisp.dhis.mobile.ui.designsystem.component.internal.formatStoredDateToUI import org.hisp.dhis.mobile.ui.designsystem.component.internal.formatUIDateToStored +import org.hisp.dhis.mobile.ui.designsystem.component.internal.getDefaultFormat +import org.hisp.dhis.mobile.ui.designsystem.component.internal.getSelectableDates import org.hisp.dhis.mobile.ui.designsystem.component.internal.getSupportingTextList import org.hisp.dhis.mobile.ui.designsystem.component.internal.getTime import org.hisp.dhis.mobile.ui.designsystem.component.internal.getTimePickerState +import org.hisp.dhis.mobile.ui.designsystem.component.internal.isValidHourFormat +import org.hisp.dhis.mobile.ui.designsystem.component.internal.parseDate import org.hisp.dhis.mobile.ui.designsystem.component.internal.provideDatePickerState import org.hisp.dhis.mobile.ui.designsystem.component.internal.timePickerColors +import org.hisp.dhis.mobile.ui.designsystem.component.internal.yearIsInRange import org.hisp.dhis.mobile.ui.designsystem.component.model.DateTimeVisualTransformation import org.hisp.dhis.mobile.ui.designsystem.component.model.DateTransformation import org.hisp.dhis.mobile.ui.designsystem.component.model.RegExValidations @@ -104,8 +111,9 @@ fun InputDateTime( dateOutOfRangeText = "$dateOutOfRangeText (" + formatStringToDate( uiModel.selectableDates.initialDate, ) + " - " + - formatStringToDate(uiModel.selectableDates.endDate) + ")" - val incorrectHourFormatText = uiModel.incorrectHourFormatText ?: provideStringResource("wrong_hour_format") + formatStringToDate(uiModel.selectableDates.endDate) + ")" + val incorrectHourFormatText = + uiModel.incorrectHourFormatText ?: provideStringResource("wrong_hour_format") val incorrectHourFormatItem = SupportingTextData( text = incorrectHourFormatText, SupportingTextState.ERROR, @@ -119,13 +127,21 @@ fun InputDateTime( SupportingTextState.ERROR, ) val supportingTextList = - getSupportingTextList(uiModel, dateOutOfRangeItem, incorrectHourFormatItem, incorrectDateFormatItem) + getSupportingTextList( + uiModel, + dateOutOfRangeItem, + incorrectHourFormatItem, + incorrectDateFormatItem + ) InputShell( modifier = modifier.testTag("INPUT_DATE_TIME") .focusRequester(focusRequester), title = uiModel.title, - state = if (supportingTextList.contains(dateOutOfRangeItem) || supportingTextList.contains(incorrectDateFormatItem)) 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 = { @@ -134,7 +150,10 @@ fun InputDateTime( modifier = Modifier .testTag("INPUT_DATE_TIME_TEXT_FIELD") .fillMaxWidth(), - inputTextValue = TextFieldValue(uiModel.inputTextFieldValue?.text ?: "", TextRange(uiModel.inputTextFieldValue?.text?.length ?: 0)), + inputTextValue = TextFieldValue( + uiModel.inputTextFieldValue?.text ?: "", + TextRange(uiModel.inputTextFieldValue?.text?.length ?: 0) + ), isSingleLine = true, onInputChanged = { newText -> if (newText.text.length > uiModel.visualTransformation.maskLength) { @@ -147,7 +166,10 @@ fun InputDateTime( }, enabled = uiModel.state != InputShellState.DISABLED, state = uiModel.state, - keyboardOptions = KeyboardOptions(imeAction = uiModel.imeAction, keyboardType = KeyboardType.Number), + keyboardOptions = KeyboardOptions( + imeAction = uiModel.imeAction, + keyboardType = KeyboardType.Number + ), visualTransformation = uiModel.visualTransformation, onNextClicked = { if (uiModel.onNextClicked != null) { @@ -193,7 +215,8 @@ fun InputDateTime( primaryButton = { if (!uiModel.inputTextFieldValue?.text.isNullOrBlank() && uiModel.state != InputShellState.DISABLED) { IconButton( - modifier = Modifier.testTag("INPUT_DATE_TIME_RESET_BUTTON").padding(Spacing.Spacing0), + modifier = Modifier.testTag("INPUT_DATE_TIME_RESET_BUTTON") + .padding(Spacing.Spacing0), icon = { Icon( imageVector = Icons.Outlined.Cancel, @@ -275,7 +298,14 @@ fun InputDateTime( showDatePicker = false if (uiModel.actionType != DateTimeActionType.DATE_TIME) { datePickerState.selectedDateMillis?.let { - uiModel.onValueChanged(TextFieldValue(getDate(it, uiModel.format), selection = TextRange(uiModel.inputTextFieldValue?.text?.length ?: 0))) + uiModel.onValueChanged( + TextFieldValue( + getDate(it, uiModel.format), + selection = TextRange( + uiModel.inputTextFieldValue?.text?.length ?: 0 + ) + ) + ) } } else { showTimePicker = true @@ -290,7 +320,7 @@ fun InputDateTime( ColorStyle.DEFAULT, uiModel.cancelText ?: provideStringResource("cancel"), - ) { + ) { showDatePicker = false } }, @@ -305,7 +335,10 @@ fun InputDateTime( Text( text = uiModel.title, style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(start = Spacing.Spacing24, top = Spacing.Spacing24), + modifier = Modifier.padding( + start = Spacing.Spacing24, + top = Spacing.Spacing24 + ), ) }, state = datePickerState, @@ -318,24 +351,45 @@ fun InputDateTime( if (showTimePicker) { var timePickerState = rememberTimePickerState(0, 0, is24Hour = uiModel.is24hourFormat) - if (!uiModel.inputTextFieldValue?.text.isNullOrEmpty() && uiModel.actionType == DateTimeActionType.TIME && isValidHourFormat(uiModel.inputTextFieldValue?.text ?: "")) { + if (!uiModel.inputTextFieldValue?.text.isNullOrEmpty() && uiModel.actionType == DateTimeActionType.TIME && isValidHourFormat( + uiModel.inputTextFieldValue?.text ?: "" + ) + ) { timePickerState = rememberTimePickerState( initialHour = uiModel.inputTextFieldValue?.text?.substring(0, 2)!! .toInt(), - uiModel.inputTextFieldValue.text.substring(2, 4).toInt(), is24Hour = uiModel.is24hourFormat, + uiModel.inputTextFieldValue.text.substring(2, 4).toInt(), + is24Hour = uiModel.is24hourFormat, ) } else { - if (uiModel.inputTextFieldValue?.text?.length == 12 && isValidHourFormat(uiModel.inputTextFieldValue.text.substring(8, 12))) { + if (uiModel.inputTextFieldValue?.text?.length == 12 && isValidHourFormat( + uiModel.inputTextFieldValue.text.substring( + 8, + 12 + ) + ) + ) { timePickerState = rememberTimePickerState( - initialHour = uiModel.inputTextFieldValue.text.substring(uiModel.inputTextFieldValue.text.length - 4, uiModel.inputTextFieldValue.text.length - 2) + initialHour = uiModel.inputTextFieldValue.text.substring( + uiModel.inputTextFieldValue.text.length - 4, + uiModel.inputTextFieldValue.text.length - 2 + ) .toInt(), - uiModel.inputTextFieldValue.text.substring(uiModel.inputTextFieldValue.text.length - 2, uiModel.inputTextFieldValue.text.length).toInt(), is24Hour = uiModel.is24hourFormat, + uiModel.inputTextFieldValue.text.substring( + uiModel.inputTextFieldValue.text.length - 2, + uiModel.inputTextFieldValue.text.length + ).toInt(), + is24Hour = uiModel.is24hourFormat, ) } } Dialog( onDismissRequest = { showDatePicker = false }, - properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true, usePlatformDefaultWidth = true), + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true, + usePlatformDefaultWidth = true + ), ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -363,7 +417,7 @@ fun InputDateTime( ColorStyle.DEFAULT, uiModel.cancelText ?: provideStringResource("cancel"), - ) { + ) { showTimePicker = false } Button( @@ -374,9 +428,25 @@ fun InputDateTime( ) { showTimePicker = false if (uiModel.actionType != DateTimeActionType.DATE_TIME) { - uiModel.onValueChanged(TextFieldValue(getTime(timePickerState), selection = TextRange(uiModel.inputTextFieldValue?.text?.length ?: 0))) + uiModel.onValueChanged( + TextFieldValue( + getTime(timePickerState), + selection = TextRange( + uiModel.inputTextFieldValue?.text?.length ?: 0 + ) + ) + ) } else { - uiModel.onValueChanged(TextFieldValue(getDate(datePickerState.selectedDateMillis) + getTime(timePickerState), selection = TextRange(uiModel.inputTextFieldValue?.text?.length ?: 0))) + uiModel.onValueChanged( + TextFieldValue( + getDate(datePickerState.selectedDateMillis) + getTime( + timePickerState + ), + selection = TextRange( + uiModel.inputTextFieldValue?.text?.length ?: 0 + ) + ) + ) } } } @@ -385,11 +455,19 @@ fun InputDateTime( } } -private fun getInputState(supportingTextList: List, dateOutOfRangeItem: SupportingTextData, incorrectDateFormatItem: SupportingTextData, currentState: InputShellState): InputShellState { - return if (supportingTextList.contains(dateOutOfRangeItem) || supportingTextList.contains(incorrectDateFormatItem)) InputShellState.ERROR else currentState +private fun getInputState( + supportingTextList: List, + dateOutOfRangeItem: SupportingTextData, + incorrectDateFormatItem: SupportingTextData, + currentState: InputShellState +): InputShellState { + return if (supportingTextList.contains(dateOutOfRangeItem) || supportingTextList.contains( + incorrectDateFormatItem + ) + ) InputShellState.ERROR else currentState } -fun getActionButtonIcon(actionType: DateTimeActionType): ImageVector { +private fun getActionButtonIcon(actionType: DateTimeActionType): ImageVector { return when (actionType) { DateTimeActionType.DATE, DateTimeActionType.DATE_TIME -> Icons.Filled.Event DateTimeActionType.TIME -> Icons.Filled.Schedule @@ -418,7 +496,11 @@ fun InputDateTime( ) { val uiData = state.uiData - val uiValue = remember(state.inputTextFieldValue) { formatStoredDateToUI(state.inputTextFieldValue ?: TextFieldValue(), uiData.actionType) } + val uiValue = remember(state.inputTextFieldValue) { + formatStoredDateToUI( + state.inputTextFieldValue ?: TextFieldValue(), uiData.actionType + ) + } val focusManager = LocalFocusManager.current val focusRequester = remember { FocusRequester() } var showDatePicker by rememberSaveable { mutableStateOf(false) } @@ -428,8 +510,9 @@ fun InputDateTime( dateOutOfRangeText = "$dateOutOfRangeText (" + formatStringToDate( uiData.selectableDates.initialDate, ) + " - " + - formatStringToDate(uiData.selectableDates.endDate) + ")" - val incorrectHourFormatTextdd = uiData.incorrectHourFormatText ?: provideStringResource("wrong_hour_format") + formatStringToDate(uiData.selectableDates.endDate) + ")" + val incorrectHourFormatTextdd = + uiData.incorrectHourFormatText ?: provideStringResource("wrong_hour_format") val incorrectHourFormatItem = SupportingTextData( text = incorrectHourFormatTextdd, SupportingTextState.ERROR, @@ -443,13 +526,25 @@ fun InputDateTime( SupportingTextState.ERROR, ) val supportingTextList = - getSupportingTextList(state, uiValue, uiData, dateOutOfRangeItem, incorrectHourFormatItem, incorrectDateFormatItem) + getSupportingTextList( + state, + uiValue, + uiData, + dateOutOfRangeItem, + incorrectHourFormatItem, + incorrectDateFormatItem + ) InputShell( modifier = modifier.testTag("INPUT_DATE_TIME") .focusRequester(focusRequester), title = uiData.title, - state = getInputState(supportingTextList, dateOutOfRangeItem, incorrectDateFormatItem, state.inputState), + state = getInputState( + supportingTextList, + dateOutOfRangeItem, + incorrectDateFormatItem, + state.inputState + ), isRequiredField = uiData.isRequired, onFocusChanged = onFocusChanged, inputField = { @@ -469,7 +564,10 @@ fun InputDateTime( }, enabled = state.inputState != InputShellState.DISABLED, state = state.inputState, - keyboardOptions = KeyboardOptions(imeAction = uiData.imeAction, keyboardType = KeyboardType.Number), + keyboardOptions = KeyboardOptions( + imeAction = uiData.imeAction, + keyboardType = KeyboardType.Number + ), visualTransformation = uiData.visualTransformation, onNextClicked = { manageOnNext(focusManager, onNextClicked) @@ -570,7 +668,14 @@ fun InputDateTime( showDatePicker = false if (uiData.actionType != DateTimeActionType.DATE_TIME) { datePickerState.selectedDateMillis?.let { - onValueChanged(TextFieldValue(getDate(it), selection = TextRange(state.inputTextFieldValue?.text?.length ?: 0))) + onValueChanged( + TextFieldValue( + getDate(it), + selection = TextRange( + state.inputTextFieldValue?.text?.length ?: 0 + ) + ) + ) } } else { showTimePicker = true @@ -585,7 +690,7 @@ fun InputDateTime( ColorStyle.DEFAULT, uiData.cancelText ?: provideStringResource("cancel"), - ) { + ) { showDatePicker = false } }, @@ -600,7 +705,10 @@ fun InputDateTime( Text( text = uiData.title, style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(start = Spacing.Spacing24, top = Spacing.Spacing24), + modifier = Modifier.padding( + start = Spacing.Spacing24, + top = Spacing.Spacing24 + ), ) }, state = datePickerState, @@ -616,7 +724,11 @@ fun InputDateTime( Dialog( onDismissRequest = { showDatePicker = false }, - properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true, usePlatformDefaultWidth = true), + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true, + usePlatformDefaultWidth = true + ), ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -644,7 +756,7 @@ fun InputDateTime( ColorStyle.DEFAULT, uiData.cancelText ?: provideStringResource("cancel"), - ) { + ) { showTimePicker = false } Button( @@ -654,7 +766,13 @@ fun InputDateTime( uiData.acceptText ?: provideStringResource("ok"), ) { showTimePicker = false - manageOnValueChangedFromDateTimePicker(convertStringToTextFieldValue(getTime(timePickerState)), onValueChanged, uiData.actionType, datePickerState, timePickerState) + manageOnValueChangedFromDateTimePicker( + convertStringToTextFieldValue( + getTime( + timePickerState + ) + ), onValueChanged, uiData.actionType, datePickerState, timePickerState + ) } } } @@ -663,7 +781,11 @@ fun InputDateTime( } @Composable -fun InputDateResetButton(state: InputDateTimeState, onValueChanged: (TextFieldValue?) -> Unit, focusRequester: FocusRequester) { +fun InputDateResetButton( + state: InputDateTimeState, + onValueChanged: (TextFieldValue?) -> Unit, + focusRequester: FocusRequester +) { if (!state.inputTextFieldValue?.text.isNullOrBlank() && state.inputState != InputShellState.DISABLED) { IconButton( modifier = Modifier.testTag("INPUT_DATE_TIME_RESET_BUTTON").padding(Spacing.Spacing0), @@ -697,7 +819,11 @@ fun manageOnNext(focusManager: FocusManager, onNextClicked: (() -> Unit)?) { } } -private fun manageOnValueChanged(newText: TextFieldValue, onValueChanged: (TextFieldValue?) -> Unit, actionType: DateTimeActionType) { +private fun manageOnValueChanged( + newText: TextFieldValue, + onValueChanged: (TextFieldValue?) -> Unit, + actionType: DateTimeActionType +) { val allowedCharacters = RegExValidations.DATE_TIME.regex if (allowedCharacters.containsMatchIn(newText.text) || newText.text.isBlank()) { onValueChanged.invoke(formatUIDateToStored(newText, actionType)) @@ -705,14 +831,54 @@ private fun manageOnValueChanged(newText: TextFieldValue, onValueChanged: (TextF } @OptIn(ExperimentalMaterial3Api::class) -private fun manageOnValueChangedFromDateTimePicker(newValue: TextFieldValue?, onValueChanged: (TextFieldValue?) -> Unit, actionType: DateTimeActionType, datePickerState: DatePickerState, timePickerState: TimePickerState) { +private fun manageOnValueChangedFromDateTimePicker( + newValue: TextFieldValue?, + onValueChanged: (TextFieldValue?) -> Unit, + actionType: DateTimeActionType, + datePickerState: DatePickerState, + timePickerState: TimePickerState +) { if (actionType != DateTimeActionType.DATE_TIME) { - onValueChanged(TextFieldValue(getTime(timePickerState), selection = TextRange(newValue?.text?.length ?: 0))) + onValueChanged( + TextFieldValue( + getTime(timePickerState), + selection = TextRange(newValue?.text?.length ?: 0) + ) + ) } else { - onValueChanged(TextFieldValue(getDate(datePickerState.selectedDateMillis) + getTime(timePickerState), selection = TextRange(newValue?.text?.length ?: 0))) + onValueChanged( + TextFieldValue( + getDate(datePickerState.selectedDateMillis) + getTime( + timePickerState + ), selection = TextRange(newValue?.text?.length ?: 0) + ) + ) } } +@Suppress("deprecation") +@Deprecated( + "This function is deprecated and will be removed in the next release.", + replaceWith = ReplaceWith("provideDatePickerState(state: InputDateTimeState, data: InputDateTimeData)") +) +@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)) +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun datePickerColors(): DatePickerColors { @@ -726,6 +892,18 @@ fun datePickerColors(): DatePickerColors { ) } +@Deprecated( + "This function is deprecated and will be removed in the near future", + replaceWith = ReplaceWith("parseStringDateToMillis(dateString: String, pattern: String)") +) +private fun parseStringDateToMillis(dateString: String, pattern: String = "ddMMyyyy"): Long { + val cal = Calendar.getInstance() + return dateString.parseDate(pattern)?.let { + cal.time = it + cal.timeInMillis + } ?: 0L +} + internal fun getDate(milliSeconds: Long?, format: String? = "ddMMyyyy"): String { val cal = Calendar.getInstance() val currentTimeZone: TimeZone = cal.getTimeZone() @@ -757,7 +935,10 @@ internal fun getDate(milliSeconds: Long?, format: String? = "ddMMyyyy"): String fun formatStringToDate(dateString: String): String { return if (dateString.length == 8) { - dateString.substring(0, 2) + "/" + dateString.substring(2, 4) + "/" + dateString.substring(4, 8) + dateString.substring(0, 2) + "/" + dateString.substring( + 2, + 4 + ) + "/" + dateString.substring(4, 8) } else { dateString }