From 9c13cf318a209be4757bc3160ee2754198ee1a6f Mon Sep 17 00:00:00 2001 From: Xavier Molloy Date: Tue, 10 Oct 2023 11:49:07 +0200 Subject: [PATCH] Adapt input org unit for implementation --- .../ui/designsystem/component/InputOrgUnit.kt | 137 +++++++++++++++--- .../component/InputOrgUnitTest.kt | 43 +++--- 2 files changed, 139 insertions(+), 41 deletions(-) diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnit.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnit.kt index 29e211bcf..e0a6e5793 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnit.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnit.kt @@ -1,12 +1,28 @@ package org.hisp.dhis.mobile.ui.designsystem.component -import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDropDown +import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.testTag -import androidx.compose.ui.text.input.ImeAction import org.hisp.dhis.mobile.ui.designsystem.resource.provideDHIS2Icon +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing +import org.hisp.dhis.mobile.ui.designsystem.theme.TextColor /** * DHIS2 Input org unit. Wraps DHIS ยท [BasicTextInput]. @@ -17,11 +33,9 @@ import org.hisp.dhis.mobile.ui.designsystem.resource.provideDHIS2Icon * @param legendData manages the legendComponent * @param inputText manages the value of the text in the input field * @param isRequiredField controls whether the field is mandatory or not - * @param onNextClicked gives access to the imeAction event * @param onValueChanged gives access to the onValueChanged event * @param onFocusChanged gives access to the onFocusChanged returns true if * item is focused - * @param imeAction controls the imeAction button to be shown * @param modifier allows a modifier to be passed externally * @param onOrgUnitActionCLicked callback to when org unit button is clicked */ @@ -33,27 +47,67 @@ fun InputOrgUnit( legendData: LegendData? = null, inputText: String? = null, isRequiredField: Boolean = false, - onNextClicked: (() -> Unit)? = null, onValueChanged: ((String?) -> Unit)? = null, onFocusChanged: ((Boolean) -> Unit)? = null, - imeAction: ImeAction = ImeAction.Next, modifier: Modifier = Modifier, onOrgUnitActionCLicked: () -> Unit, ) { - BasicTextInput( - title = title, - state = state, - supportingText = supportingText, - legendData = legendData, - inputText = inputText, + val inputValue by remember(inputText) { mutableStateOf(inputText) } + + var deleteButtonIsVisible by remember(inputText) { mutableStateOf(!inputText.isNullOrEmpty() && state != InputShellState.DISABLED) } + val focusRequester = remember { FocusRequester() } + + val primaryButton: + @Composable() + (() -> Unit)? + if (deleteButtonIsVisible) { + primaryButton = { + IconButton( + modifier = Modifier + .testTag("INPUT_ORG_UNIT_RESET_BUTTON") + .padding(Spacing.Spacing0), + icon = { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = "Reset Button", + ) + }, + onClick = { + onValueChanged?.invoke("") + deleteButtonIsVisible = false + focusRequester.requestFocus() + }, + enabled = state != InputShellState.DISABLED, + ) + } + } else { + primaryButton = { + IconButton( + modifier = Modifier + .testTag("INPUT_ORG_UNIT_DROPDOWN_BUTTON") + .padding(Spacing.Spacing0), + icon = { + Icon( + imageVector = Icons.Outlined.ArrowDropDown, + contentDescription = "Dropdown Button", + ) + }, + onClick = { + onOrgUnitActionCLicked.invoke() + focusRequester.requestFocus() + }, + enabled = state != InputShellState.DISABLED, + ) + } + } + InputShell( + modifier = modifier + .testTag("INPUT_ORG_UNIT") + .focusRequester(focusRequester), isRequiredField = isRequiredField, - onNextClicked = onNextClicked, - onValueChanged = onValueChanged, - keyboardOptions = KeyboardOptions(imeAction = imeAction), - modifier = modifier, - testTag = "ORG_UNIT", - onFocusChanged = onFocusChanged, - actionButton = { + title = title, + primaryButton = primaryButton, + secondaryButton = { SquareIconButton( modifier = Modifier.testTag("ORG_UNIT_BUTTON"), enabled = state != InputShellState.DISABLED, @@ -63,8 +117,51 @@ fun InputOrgUnit( contentDescription = "org_unit_icon", ) }, - onClick = onOrgUnitActionCLicked, + onClick = { + onOrgUnitActionCLicked.invoke() + focusRequester.requestFocus() + }, ) }, + state = state, + legend = { + legendData?.let { + Legend(legendData, Modifier.testTag("INPUT_ORG_UNIT_LEGEND")) + } + }, + supportingText = { + supportingText?.forEach { label -> + SupportingText( + label.text, + label.state, + modifier = Modifier.testTag("INPUT_ORG_UNIT_SUPPORTING_TEXT"), + ) + } + }, + inputField = { + Box { + Text( + modifier = Modifier.testTag("INPUT_DROPDOWN_TEXT").fillMaxWidth(), + text = inputValue ?: "", + style = MaterialTheme.typography.bodyLarge.copy( + color = if (state != InputShellState.DISABLED) { + TextColor.OnSurface + } else { + TextColor.OnDisabledSurface + }, + ), + ) + Box( + modifier = Modifier + .matchParentSize() + .alpha(0f) + .clickable(onClick = { + onOrgUnitActionCLicked.invoke() + focusRequester.requestFocus() + }), + ) + } + }, + onFocusChanged = onFocusChanged, ) } diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnitTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnitTest.kt index eafc4aca5..c88b614a1 100644 --- a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnitTest.kt +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputOrgUnitTest.kt @@ -5,16 +5,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.assertTextEquals -import androidx.compose.ui.test.hasText 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.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor import org.junit.Rule import org.junit.Test @@ -37,7 +34,7 @@ class InputOrgUnitTest { } @Test - fun shouldAllowUserInputWhenEnabled() { + fun shouldDisplayDropdownButtonWhenEmpty() { rule.setContent { var inputValue by rememberSaveable { mutableStateOf("") } InputOrgUnit( @@ -52,42 +49,46 @@ class InputOrgUnitTest { ) } rule.onNodeWithTag("INPUT_ORG_UNIT").assertExists() - rule.onNodeWithTag("INPUT_ORG_UNIT_FIELD").performTextInput("PHC fake") - rule.onNodeWithTag("INPUT_ORG_UNIT_FIELD").assert(hasText("PHC fake")) + rule.onNodeWithTag("ORG_UNIT_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_ORG_UNIT_DROPDOWN_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_ORG_UNIT_DROPDOWN_BUTTON").assertIsEnabled() } @Test - fun shouldNotAllowUserInputWhenDisabled() { + fun shouldDisplayResetButtonWhenNotEmpty() { rule.setContent { + var inputValue by rememberSaveable { mutableStateOf("Sample data") } InputOrgUnit( title = "Label", - state = InputShellState.DISABLED, + inputText = inputValue, + onValueChanged = { + if (it != null) { + inputValue = it + } + }, onOrgUnitActionCLicked = {}, ) } rule.onNodeWithTag("INPUT_ORG_UNIT").assertExists() - rule.onNodeWithTag("INPUT_ORG_UNIT_FIELD").assertIsNotEnabled() + rule.onNodeWithTag("ORG_UNIT_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_ORG_UNIT_RESET_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_ORG_UNIT_RESET_BUTTON").assertIsEnabled() } @Test - fun shouldShowResetButtonWhenTextFieldHasContent() { + fun shouldBeClickableIfDisabled() { rule.setContent { - var inputValue by rememberSaveable { mutableStateOf("") } InputOrgUnit( title = "Label", - inputText = inputValue, - onValueChanged = { - if (it != null) { - inputValue = it - } - }, + state = InputShellState.DISABLED, onOrgUnitActionCLicked = {}, ) } rule.onNodeWithTag("INPUT_ORG_UNIT").assertExists() - rule.onNodeWithTag("INPUT_ORG_UNIT_FIELD").assertExists() - rule.onNodeWithTag("INPUT_ORG_UNIT_FIELD").performTextInput("PHC fake") - rule.onNodeWithTag("INPUT_ORG_UNIT_RESET_BUTTON").assertExists() + rule.onNodeWithTag("ORG_UNIT_BUTTON").assertExists() + rule.onNodeWithTag("ORG_UNIT_BUTTON").assertIsNotEnabled() + rule.onNodeWithTag("INPUT_ORG_UNIT_DROPDOWN_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_ORG_UNIT_DROPDOWN_BUTTON").assertIsNotEnabled() } @Test @@ -109,7 +110,7 @@ class InputOrgUnitTest { rule.onNodeWithTag("INPUT_ORG_UNIT").assertExists() rule.onNodeWithTag("INPUT_ORG_UNIT_RESET_BUTTON").assertExists() rule.onNodeWithTag("INPUT_ORG_UNIT_RESET_BUTTON").performClick() - rule.onNodeWithTag("INPUT_ORG_UNIT_FIELD").assertTextEquals("") + rule.onNodeWithTag("INPUT_DROPDOWN_TEXT").assertTextEquals("") rule.onNodeWithTag("INPUT_ORG_UNIT_RESET_BUTTON").assertDoesNotExist() }