From f816ea370b9b74f5871ec3e51a5d5e3df8213c02 Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Tue, 19 Sep 2023 16:06:55 +0530 Subject: [PATCH] Add input phone number component --- .../kotlin/org/hisp/dhis/common/App.kt | 2 + .../hisp/dhis/common/screens/Components.kt | 1 + .../common/screens/InputPhoneNumberScreen.kt | 91 +++++++++++++ .../component/InputPhoneNumber.kt | 74 +++++++++++ .../resources/values/strings_en.xml | 3 +- .../component/InputPhoneNumberTest.kt | 123 ++++++++++++++++++ 6 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputPhoneNumberScreen.kt create mode 100644 designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumber.kt create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumberTest.kt diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt index da09ea305..037af95be 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt @@ -38,6 +38,7 @@ import org.hisp.dhis.common.screens.InputLongTextScreen import org.hisp.dhis.common.screens.InputNegativeIntegerScreen import org.hisp.dhis.common.screens.InputNumberScreen import org.hisp.dhis.common.screens.InputPercentageScreen +import org.hisp.dhis.common.screens.InputPhoneNumberScreen import org.hisp.dhis.common.screens.InputPositiveIntegerOrZeroScreen import org.hisp.dhis.common.screens.InputPositiveIntegerScreen import org.hisp.dhis.common.screens.InputRadioButtonScreen @@ -139,6 +140,7 @@ fun Main() { Components.BADGES -> BadgesScreen() Components.SWITCH -> SwitchScreen() Components.INPUT_RADIO_BUTTON -> InputRadioButtonScreen() + Components.INPUT_PHONE_NUMBER -> InputPhoneNumberScreen() } } } diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt index bacc51319..27e2e87fd 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt @@ -30,4 +30,5 @@ enum class Components(val label: String) { ICON_CARDS("Icon Cards"), INPUT_RADIO_BUTTON("Input Radio Button"), SWITCH("Switch"), + INPUT_PHONE_NUMBER("Input Phone Number"), } diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputPhoneNumberScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputPhoneNumberScreen.kt new file mode 100644 index 000000000..8a8488bc8 --- /dev/null +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputPhoneNumberScreen.kt @@ -0,0 +1,91 @@ +package org.hisp.dhis.common.screens + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import org.hisp.dhis.mobile.ui.designsystem.component.ColumnComponentContainer +import org.hisp.dhis.mobile.ui.designsystem.component.Description +import org.hisp.dhis.mobile.ui.designsystem.component.InputPhoneNumber +import org.hisp.dhis.mobile.ui.designsystem.component.InputShellState +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing +import org.hisp.dhis.mobile.ui.designsystem.theme.TextColor + +@Composable +fun InputPhoneNumberScreen() { + ColumnComponentContainer( + title = "Input Phone Number", + ) { + var inputValue1 by rememberSaveable { mutableStateOf("") } + Description("Default Input Phone Number", textColor = TextColor.OnSurfaceVariant) + InputPhoneNumber( + title = "Label", + inputText = inputValue1, + onValueChanged = { + if (it != null) { + inputValue1 = it + } + }, + onCallActionClicked = { + // no-op + }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + + var inputValue2 by rememberSaveable { mutableStateOf("") } + Description("Disabled Input Phone Number Without Phone Number", textColor = TextColor.OnSurfaceVariant) + InputPhoneNumber( + title = "Label", + inputText = inputValue2, + state = InputShellState.DISABLED, + onValueChanged = { + if (it != null) { + inputValue2 = it + } + }, + onCallActionClicked = { + // no-op + }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + + var inputValue3 by rememberSaveable { mutableStateOf("1111111111") } + Description("Disabled Input Phone Number With Phone Number", textColor = TextColor.OnSurfaceVariant) + InputPhoneNumber( + title = "Label", + inputText = inputValue3, + state = InputShellState.DISABLED, + onValueChanged = { + if (it != null) { + inputValue3 = it + } + }, + onCallActionClicked = { + // no-op + }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + + var inputValue4 by rememberSaveable { mutableStateOf("") } + Description("Error Input Phone Number", textColor = TextColor.OnSurfaceVariant) + InputPhoneNumber( + title = "Label", + inputText = inputValue4, + state = InputShellState.ERROR, + isRequiredField = true, + onValueChanged = { + if (it != null) { + inputValue4 = it + } + }, + onCallActionClicked = { + // no-op + }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + } +} diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumber.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumber.kt new file mode 100644 index 000000000..de1c0bdc9 --- /dev/null +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumber.kt @@ -0,0 +1,74 @@ +package org.hisp.dhis.mobile.ui.designsystem.component + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import org.hisp.dhis.mobile.ui.designsystem.component.internal.RegExValidations +import org.hisp.dhis.mobile.ui.designsystem.resource.provideDHIS2Icon +import org.hisp.dhis.mobile.ui.designsystem.resource.provideStringResource + +@Composable +fun InputPhoneNumber( + title: String, + onCallActionClicked: () -> Unit, + modifier: Modifier = Modifier, + characterLimit: Int = 10, + state: InputShellState = InputShellState.UNFOCUSED, + legendData: LegendData? = null, + inputText: String? = null, + isRequiredField: Boolean = false, + onNextClicked: (() -> Unit)? = null, + onValueChanged: ((String?) -> Unit)? = null, + imeAction: ImeAction = ImeAction.Next, + notation: RegExValidations = RegExValidations.ONLY_INTEGERS, +) { + val hasPhoneNumber = inputText?.length == characterLimit + val supportingText = if (state == InputShellState.ERROR) { + listOf( + SupportingTextData( + text = provideStringResource("enter_phone_number"), + state = SupportingTextState.ERROR, + ), + ) + } else { + emptyList() + } + + BasicTextInput( + title = title, + state = state, + supportingText = supportingText, + legendData = legendData, + inputText = inputText, + isRequiredField = isRequiredField, + onNextClicked = onNextClicked, + onValueChanged = { + if ((it?.length ?: 0) <= characterLimit) { + onValueChanged?.invoke(it) + } else { + // no-op + } + }, + keyboardOptions = KeyboardOptions(imeAction = imeAction, keyboardType = KeyboardType.Number), + allowedCharacters = notation.regex, + modifier = modifier, + testTag = "PHONE_NUMBER", + actionButton = { + SquareIconButton( + modifier = Modifier.testTag("CALL_PHONE_NUMBER_BUTTON"), + enabled = hasPhoneNumber, + icon = { + Icon( + painter = provideDHIS2Icon("dhis2_phone_positive"), + contentDescription = null, + ) + }, + onClick = onCallActionClicked, + ) + }, + ) +} diff --git a/designsystem/src/commonMain/resources/values/strings_en.xml b/designsystem/src/commonMain/resources/values/strings_en.xml index 4f5375101..7b8f121ee 100644 --- a/designsystem/src/commonMain/resources/values/strings_en.xml +++ b/designsystem/src/commonMain/resources/values/strings_en.xml @@ -13,4 +13,5 @@ Show fields Hide fields Next - \ No newline at end of file + Enter phone number + diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumberTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumberTest.kt new file mode 100644 index 000000000..b43c3db95 --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputPhoneNumberTest.kt @@ -0,0 +1,123 @@ +package org.hisp.dhis.mobile.ui.designsystem.component + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import org.junit.Rule +import org.junit.Test + +class InputPhoneNumberTest { + + @get:Rule + val rule = createComposeRule() + + @Test + fun shouldAllowDigitsInputOnly() { + rule.setContent { + var inputValue by remember { mutableStateOf("") } + + InputPhoneNumber( + title = "Phone Number", + inputText = inputValue, + onValueChanged = { + if (it != null) { + inputValue = it + } + }, + onCallActionClicked = { + // no-op + }, + ) + } + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").assertTextEquals("") + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").performTextInput("1111") + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").assertTextEquals("1111") + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").performTextInput("1111a") + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").assertTextEquals("1111") + } + + @Test + fun shouldEnableCallActionButtonAfterInputTextReachesCharacterLimit() { + rule.setContent { + var inputValue by remember { mutableStateOf("") } + + InputPhoneNumber( + title = "Phone Number", + inputText = inputValue, + onValueChanged = { + if (it != null) { + inputValue = it + } + }, + onCallActionClicked = { + // no-op + }, + ) + } + rule.onNodeWithTag("CALL_PHONE_NUMBER_BUTTON").assertIsNotEnabled() + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").performTextInput("1111111111") + rule.onNodeWithTag("CALL_PHONE_NUMBER_BUTTON").assertIsEnabled() + } + + @Test + fun shouldDisplaySupportTextIfInputStateIsError() { + rule.setContent { + InputPhoneNumber( + title = "Phone Number", + inputText = "", + state = InputShellState.UNFOCUSED, + onValueChanged = { + // no-op + }, + onCallActionClicked = { + // no-op + }, + ) + } + rule.onNodeWithTag("INPUT_PHONE_NUMBER_SUPPORTING_TEXT").assertDoesNotExist() + + rule.setContent { + InputPhoneNumber( + title = "Phone Number", + inputText = "", + onValueChanged = { + // no-op + }, + onCallActionClicked = { + // no-op + }, + ) + } + rule.onNodeWithTag("INPUT_PHONE_NUMBER_SUPPORTING_TEXT").assertExists() + } + + @Test + fun shouldClearPhoneNumberWhenClearButtonIsClicked() { + rule.setContent { + var inputValue by remember { mutableStateOf("1234") } + + InputPhoneNumber( + title = "Phone Number", + inputText = inputValue, + onValueChanged = { + if (it != null) inputValue = it + }, + onCallActionClicked = { + // no-op + }, + ) + } + rule.onNodeWithTag("INPUT_PHONE_NUMBER_RESET_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_PHONE_NUMBER_RESET_BUTTON").performClick() + rule.onNodeWithTag("INPUT_PHONE_NUMBER_FIELD").assertTextEquals("") + rule.onNodeWithTag("INPUT_PHONE_NUMBER_RESET_BUTTON").assertDoesNotExist() + } +}