Skip to content

Commit

Permalink
ANDROAPP-5500-mobile-ui-Create-Input-Long-Text (#45)
Browse files Browse the repository at this point in the history
* add InputLongText component, add documentation

* implement tests for new component

* ktlint
  • Loading branch information
xavimolloy authored Sep 8, 2023
1 parent 557cb35 commit 9fa87fa
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 5 deletions.
5 changes: 3 additions & 2 deletions common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.hisp.dhis.common.screens.FormShellsScreen
import org.hisp.dhis.common.screens.FormsComponentsScreen
import org.hisp.dhis.common.screens.IconButtonScreen
import org.hisp.dhis.common.screens.InputIntegerScreen
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
Expand All @@ -55,7 +56,7 @@ fun App() {

@Composable
fun Main() {
val currentScreen = remember { mutableStateOf(Components.INPUT_NUMBER) }
val currentScreen = remember { mutableStateOf(Components.INPUT_LONG_TEXT) }
var expanded by remember { mutableStateOf(false) }

Column(
Expand All @@ -66,7 +67,6 @@ fun Main() {
Box(
modifier = Modifier
.fillMaxWidth(),

) {
TextField(
readOnly = true,
Expand Down Expand Up @@ -113,6 +113,7 @@ fun Main() {
Components.INPUT -> InputScreen()
Components.SUPPORTING_TEXT -> SupportingTextScreen()
Components.INPUT_TEXT -> InputTextScreen()
Components.INPUT_LONG_TEXT -> InputLongTextScreen()
Components.LEGEND_DESCRIPTION -> LegendDescriptionScreen()
Components.FORM_SHELLS -> FormShellsScreen()
Components.BUTTON_BLOCK -> ButtonBlockScreen()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ enum class Components(val label: String) {
BOTTOM_SHEET("Bottom Sheet"),
TAGS("Tags"),
SECTIONS("Sections"),
INPUT_LONG_TEXT("Input Long Text"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import org.hisp.dhis.mobile.ui.designsystem.component.SubTitle
fun FormsComponentsScreen() {
ColumnComponentContainer("Input Shell") {
SubTitle("Sample functional Input Shell ")
InputShellPreview("Label", inputField = { BasicInput("Helper", true, InputStyle.WITH_HELPER_BEFORE, onInputChanged = {}) })
InputShellPreview("Label", inputField = { BasicInput("Helper", true, helperStyle = InputStyle.WITH_HELPER_BEFORE, onInputChanged = {}) })
SubTitle("Unfocused Input shell ")
InputShellPreview("Label")
SubTitle("Focused ")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package org.hisp.dhis.common.screens

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.text.input.ImeAction
import org.hisp.dhis.mobile.ui.designsystem.component.ColumnComponentContainer
import org.hisp.dhis.mobile.ui.designsystem.component.InputLongText
import org.hisp.dhis.mobile.ui.designsystem.component.InputShellState
import org.hisp.dhis.mobile.ui.designsystem.component.LegendData
import org.hisp.dhis.mobile.ui.designsystem.component.SubTitle
import org.hisp.dhis.mobile.ui.designsystem.component.SupportingTextData
import org.hisp.dhis.mobile.ui.designsystem.component.SupportingTextState
import org.hisp.dhis.mobile.ui.designsystem.component.Title
import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor
import org.hisp.dhis.mobile.ui.designsystem.theme.TextColor

@Composable
fun InputLongTextScreen() {
ColumnComponentContainer {
Title("Input Long Text component", textColor = TextColor.OnSurfaceVariant)
SubTitle(" Basic Input Long Text", textColor = TextColor.OnSurfaceVariant)
var inputValue1 by rememberSaveable {
mutableStateOf(
"Lorem ipsum dolor sit amet," +
" consectetur adipiscing elit. " +
"Maecenas dolor lacus, aliquam.",
)
}

InputLongText(
title = "Label",
inputText = inputValue1,
onValueChanged = {
if (it != null) {
inputValue1 = it
}
},
)
SubTitle(" Basic Input Long Text with error message", textColor = TextColor.OnSurfaceVariant)
var inputValueError by rememberSaveable {
mutableStateOf(
"Lorem ipsum dolor sit amet," +
" consectetur adipiscing elit. " +
"Maecenas dolor lacus, aliquam." +
"Lorem ipsum dolor sit amet," +
" consectetur adipiscing elit. " +
"Maecenas dolor lacus, aliquam." +
"Lorem ipsum dolor sit amet," +
" consectetur adipiscing elit. " +
"Maecenas dolor lacus, aliquam." +
"Lorem ipsum dolor sit amet," +
" consectetur adipiscing elit. " +
"Maecenas dolor lacus, aliquam.",
)
}

InputLongText(
title = "Label",
inputText = inputValueError,
onValueChanged = {
if (it != null) {
inputValueError = it
}
},
supportingText = listOf(
SupportingTextData(
"100000/1000000 characters used",
state = SupportingTextState.ERROR,
),
),
state = InputShellState.ERROR,
)

var inputValue2 by rememberSaveable { mutableStateOf("") }
SubTitle("Input long text with legend", textColor = TextColor.OnSurfaceVariant)
InputLongText(
title = "Label",
inputText = inputValue2,
legendData = LegendData(SurfaceColor.CustomGreen, "Legend"),
onValueChanged = {
if (it != null) {
inputValue2 = it
}
},
)

var inputValue3 by rememberSaveable { mutableStateOf("") }

SubTitle("Input Long text with Supporting text", textColor = TextColor.OnSurfaceVariant)
InputLongText(
title = "Label",
inputText = inputValue3,
supportingText = listOf(SupportingTextData("Supporting text", SupportingTextState.DEFAULT)),
onValueChanged = {
if (it != null) {
inputValue3 = it
}
},
)

var inputValue4 by rememberSaveable { mutableStateOf("") }

SubTitle("Input Long Text with Supporting text and legend", textColor = TextColor.OnSurfaceVariant)

InputLongText(
title = "Label",
inputText = inputValue4,
supportingText = listOf(
SupportingTextData(
"Supporting text",
SupportingTextState.DEFAULT,
),
),
legendData = LegendData(SurfaceColor.CustomGreen, "Legend"),
onValueChanged = {
if (it != null) {
inputValue4 = it
}
},
)
SubTitle("Input Long text with error and warning text and legend", textColor = TextColor.OnSurfaceVariant)
var inputValue5 by rememberSaveable { mutableStateOf("") }

InputLongText(
title = "Label",
inputText = inputValue5,
supportingText = listOf(
SupportingTextData("Supporting text", SupportingTextState.DEFAULT),
SupportingTextData("Supporting text", SupportingTextState.WARNING),
SupportingTextData("Supporting text", SupportingTextState.ERROR),

),
legendData = LegendData(SurfaceColor.CustomGreen, "Legend"),
state = InputShellState.ERROR,
imeAction = ImeAction.Done,
onValueChanged = {
if (it != null) {
inputValue5 = it
}
},
)
var inputValue6 by rememberSaveable { mutableStateOf("") }

SubTitle("Disabled Input Long Text ", textColor = TextColor.OnSurfaceVariant)
InputLongText(
title = "Label",
inputText = inputValue6,
state = InputShellState.DISABLED,
onValueChanged = {
if (it != null) {
inputValue6 = it
}
},
)

var inputValue7 by rememberSaveable { mutableStateOf("Content") }

SubTitle("Disabled Input text with content ", textColor = TextColor.OnSurfaceVariant)
InputLongText(
title = "Label",
inputText = inputValue7,
state = InputShellState.DISABLED,
onValueChanged = {
if (it != null) {
inputValue7 = it
}
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,21 @@ fun EmptyInput(
* @param helper Manages the helper text to be shown
* @param enabled Controls the enabled state of the component. When `false`, this component will not be
* clickable and will appear disabled to accessibility services.
* @param isSingleLine manages the number of lines to be allowed in the input field
* @param helperStyle manages the helper text style, NONE by default
* @param inputText manages the value of the input field text
* @param onInputChanged gives access to the onTextChangedEvent
* @param modifier to pass a modifier if necessary
* @param state manages the color of cursor depending on the state of parent component
* @param keyboardOptions manages the ImeAction to be shown on the keyboard
* @param onNextClicked gives access to the ImeAction event
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun BasicInput(
helper: String? = null,
enabled: Boolean = true,
isSingleLine: Boolean = true,
helperStyle: InputStyle = InputStyle.NONE,
inputText: String = "",
onInputChanged: (String) -> Unit,
Expand Down Expand Up @@ -119,7 +125,7 @@ fun BasicInput(
onValueChange = onInputChanged,
enabled = enabled,
textStyle = MaterialTheme.typography.bodyLarge.copy(color = if (enabled) TextColor.OnSurface else TextColor.OnDisabledSurface),
singleLine = true,
singleLine = isSingleLine,
decorationBox = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.hisp.dhis.mobile.ui.designsystem.component

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material3.Icon
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.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.input.ImeAction
import org.hisp.dhis.mobile.ui.designsystem.theme.InternalSizeValues
import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing

/**
* DHIS2 Input Long Text. Wraps DHIS · [InputShell].
* @param title controls the text to be shown for the title
* @param state Manages the InputShell state
* @param supportingText is a list of SupportingTextData that
* manages all the messages to be shown
* @param legendData manages the legendComponent
* @param inputText manages the value of the text in the input field
* @param isRequiredField manages whether the field is mandatory or not
* @param onNextClicked gives access to the ImeAction event
* @param onValueChanged gives access to the onValueChanged event
* @param imeAction controls the ImeAction to show in the keyboard
* @param modifier allows a modifier to be passed externally
*/
@Composable
fun InputLongText(
title: String,
state: InputShellState = InputShellState.UNFOCUSED,
supportingText: List<SupportingTextData>? = null,
legendData: LegendData? = null,
inputText: String? = null,
isRequiredField: Boolean = false,
onNextClicked: (() -> Unit)? = null,
onValueChanged: ((String?) -> Unit)? = null,
imeAction: ImeAction = ImeAction.Next,
modifier: Modifier = Modifier,
) {
val inputValue by remember(inputText) { mutableStateOf(inputText ?: "") }
var deleteButtonIsVisible by remember {
mutableStateOf(!inputText.isNullOrEmpty() && state != InputShellState.DISABLED)
}
val focusManager = LocalFocusManager.current

val keyboardOptions = KeyboardOptions(imeAction = imeAction)
InputShell(
modifier = modifier,
isRequiredField = isRequiredField,
title = title,
primaryButton = {
if (deleteButtonIsVisible) {
IconButton(
modifier = Modifier.testTag("INPUT_LONG_TEXT_RESET_BUTTON"),
icon = {
Icon(
imageVector = Icons.Outlined.Cancel,
contentDescription = "Icon Button",
)
},
onClick = {
onValueChanged?.invoke("")
deleteButtonIsVisible = false
},
enabled = state != InputShellState.DISABLED,
)
}
},
state = state,
legend = {
legendData?.let {
Legend(legendData, Modifier.testTag("INPUT_LONG_TEXT_LEGEND"))
}
},
supportingText = {
supportingText?.forEach {
label ->
SupportingText(
label.text,
label.state,
modifier = Modifier.testTag("INPUT_LONG_TEXT_SUPPORTING_TEXT"),
)
}
},
inputField = {
BasicInput(
modifier = Modifier.testTag("INPUT_LONG_TEXT_FIELD")
.scrollable(
orientation = Orientation.Vertical,
state = rememberScrollState(),
).heightIn(Spacing.Spacing0, InternalSizeValues.Size300),
isSingleLine = false,
inputText = inputValue,
onInputChanged = {
onValueChanged?.invoke(it)
deleteButtonIsVisible = it.isNotEmpty()
},
enabled = state != InputShellState.DISABLED,
state = state,
keyboardOptions = keyboardOptions,
onNextClicked = {
if (onNextClicked != null) {
onNextClicked.invoke()
} else {
focusManager.moveFocus(FocusDirection.Down)
}
},
)
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.hisp.dhis.mobile.ui.designsystem.theme.TextColor
* @param legend controls the optional legend composable
* @param inputField controls the input field composable .
* @param supportingText controls the supporting text composable
* @param isRequiredField controls whether the field is mandatory
*/
@Composable
fun InputShell(
Expand Down Expand Up @@ -112,7 +113,7 @@ private fun InputShellRow(
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier.fillMaxWidth()
.background(backgroundColor)
.padding(Spacing.Spacing16, Spacing.Spacing8, Spacing.Spacing0, Spacing.Spacing4),
Expand Down
Loading

0 comments on commit 9fa87fa

Please sign in to comment.