Skip to content

Commit

Permalink
Merge pull request #33 from kiwicom/checkbox_error
Browse files Browse the repository at this point in the history
Checkbox error state
  • Loading branch information
hrach authored Sep 22, 2021
2 parents a619dcc + 8c11a40 commit 27502be
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,20 @@ fun CheckboxScreenInner() {

Spacer(Modifier.size(32.dp))

var checkbox3 by remember { mutableStateOf(false) }
Checkbox(checked = checkbox3, error = true, onCheckedChange = { checkbox3 = !checkbox3 })

Spacer(Modifier.size(32.dp))

Checkbox(checked = true, enabled = false, onCheckedChange = {})

Spacer(Modifier.size(32.dp))

Checkbox(checked = false, enabled = false, onCheckedChange = {})

Spacer(Modifier.size(32.dp))

Checkbox(checked = false, enabled = false, error = true, onCheckedChange = {})
}

Spacer(Modifier.size(32.dp))
Expand Down Expand Up @@ -102,6 +111,7 @@ fun CheckboxScreenInner() {
checked = checkbox5,
onCheckedChange = { checkbox5 = !checkbox5 },
modifier = Modifier.fillMaxWidth(),
error = true,
description = { Text("May the Force be with you.") },
) {
Text("Star Wars")
Expand Down
85 changes: 62 additions & 23 deletions ui/src/main/java/kiwi/orbit/compose/ui/controls/Checkbox.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package kiwi.orbit.compose.ui.controls

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
Expand All @@ -35,6 +34,7 @@ public fun Checkbox(
onCheckedChange: (() -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
error: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val borderColor by animateColorAsState(
Expand Down Expand Up @@ -74,51 +74,90 @@ public fun Checkbox(
Modifier
}

Box(modifier) {
val errorAlpha by animateFloatAsState(
targetValue = if (error && enabled) 1.0f else 0.0f,
animationSpec = tween(durationMillis = CheckboxAnimationDuration)
)
val errorStrokeColor = OrbitTheme.colors.critical.main
val errorShadowColor = OrbitTheme.colors.critical.subtle

Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
Canvas(
Modifier
.then(selectableModifier)
.wrapContentSize(Alignment.Center)
.padding(CheckboxButtonPadding)
.requiredSize(CheckboxButtonSize)
.requiredSize(CheckboxSize)
) {
drawCheckbox(borderColor, backgroundColor)
drawCheckbox(borderColor, backgroundColor, errorAlpha)
drawError(errorStrokeColor, errorShadowColor, errorAlpha)
}
if (checked) {
Icon(
Icons.Check,
contentDescription = null,
modifier = Modifier
.padding(start = 4.dp, top = 4.dp)
.size(16.dp),
modifier = Modifier.size(16.dp),
tint = iconColor,
)
}
}
}

private fun DrawScope.drawCheckbox(borderColor: Color, backgroundColor: Color) {
val size = CheckboxButtonSize.toPx()
val cornerRadius = CornerRadius(6.dp.toPx())
val strokeWidth = 2.dp.toPx()
val halfStrokeWidth = strokeWidth / 2.0f
private fun DrawScope.drawCheckbox(borderColor: Color, backgroundColor: Color, errorAlpha: Float) {
val hasError = errorAlpha != 0.0f
val errorShift = if (hasError) 0.5.dp.toPx() else 0f

val checkboxSize = CheckboxSize.toPx()
val checkboxBorderWidth = CheckboxBorderWidth.toPx()
val checkboxBorderHalfWidth = checkboxBorderWidth / 2.0f
val checkboxCornerRadius = CornerRadius(CheckboxCornerRadius.toPx())
drawRoundRect(
color = backgroundColor,
size = Size(size, size),
cornerRadius = cornerRadius,
topLeft = Offset(0f + errorShift, 0f + errorShift),
size = Size(checkboxSize - 2 * errorShift, checkboxSize - 2 * errorShift),
cornerRadius = checkboxCornerRadius,
style = Fill,
)
drawRoundRect(
color = borderColor,
topLeft = Offset(halfStrokeWidth, halfStrokeWidth),
size = Size(size - strokeWidth, size - strokeWidth),
cornerRadius = cornerRadius,
style = Stroke(strokeWidth),
topLeft = Offset(checkboxBorderHalfWidth, checkboxBorderHalfWidth),
size = Size(checkboxSize - checkboxBorderWidth, checkboxSize - checkboxBorderWidth),
cornerRadius = checkboxCornerRadius,
style = Stroke(checkboxBorderWidth),
)
}

private fun DrawScope.drawError(borderColor: Color, shadowColor: Color, alpha: Float) {
if (alpha == 0.0f) return

val shadowRectShift = (ErrorShadowWidth / 2.0f).toPx()
val shadowRectSize = (ErrorShadowSize - ErrorShadowWidth).toPx()
drawRoundRect(
color = shadowColor,
topLeft = Offset(-shadowRectShift, -shadowRectShift),
size = Size(shadowRectSize, shadowRectSize),
cornerRadius = CornerRadius(ErrorShadowCornerRadius.toPx()),
style = Stroke(ErrorShadowWidth.toPx()),
)

val errorRectShift = (CheckboxBorderWidth / 2.0f).toPx()
val errorRectSize = (CheckboxSize - CheckboxBorderWidth).toPx()
drawRoundRect(
color = borderColor,
topLeft = Offset(errorRectShift, errorRectShift),
size = Size(errorRectSize, errorRectSize),
cornerRadius = CornerRadius(CheckboxCornerRadius.toPx()),
style = Stroke(CheckboxBorderWidth.toPx()),
)
}

private const val CheckboxAnimationDuration = 100

private val CheckboxButtonSize = 20.dp
private val CheckboxButtonPadding = 2.dp
private val CheckboxSize = 20.dp
private val CheckboxBorderWidth = 2.dp
private val CheckboxCornerRadius = 6.dp
private val CheckboxRippleRadius = 20.dp
private val ErrorShadowWidth = 2.dp
private val ErrorShadowSize = CheckboxSize + ErrorShadowWidth * 2
private val ErrorShadowCornerRadius = CheckboxCornerRadius + ErrorShadowWidth
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public fun CheckboxField(
onCheckedChange: (() -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
error: Boolean = false,
contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
description: (@Composable () -> Unit)? = null,
label: @Composable ColumnScope.() -> Unit,
Expand All @@ -55,17 +56,19 @@ public fun CheckboxField(
end = contentPadding.calculateEndPadding(layoutDirection),
),
) {
val checkboxVerticalShift = 1.dp
Checkbox(
checked = checked,
onCheckedChange = null,
modifier = Modifier.padding(
top = (contentPadding.calculateTopPadding() - 3.dp).coerceAtLeast(0.dp),
top = (contentPadding.calculateTopPadding() - checkboxVerticalShift).coerceAtLeast(0.dp),
end = 10.dp
),
enabled = enabled,
error = error,
interactionSource = interactionSource,
)
val topPadding = contentPadding.calculateTopPadding().coerceAtLeast(3.dp)
val topPadding = contentPadding.calculateTopPadding().coerceAtLeast(checkboxVerticalShift)
val bottomPadding = contentPadding.calculateBottomPadding()
Column(Modifier.padding(top = topPadding, bottom = bottomPadding)) {
CompositionLocalProvider(
Expand Down
6 changes: 0 additions & 6 deletions ui/src/main/java/kiwi/orbit/compose/ui/controls/Radio.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
Expand Down Expand Up @@ -79,8 +76,6 @@ public fun Radio(
Canvas(
modifier
.then(selectableModifier)
.wrapContentSize(Alignment.Center)
.padding(RadioPadding)
.requiredSize(RadioSize)
) {
drawRadio(borderWidth, borderColor, backgroundColor)
Expand Down Expand Up @@ -110,7 +105,6 @@ private const val RadioAnimationDuration = 100

private val RadioSize = 20.dp
private val RadioRadiusSize = RadioSize / 2
private val RadioPadding = 2.dp
private val RadioRippleRadius = 20.dp
private val ErrorShadowSize = 24.dp
private val ErrorShadowRadius = ErrorShadowSize / 2
5 changes: 3 additions & 2 deletions ui/src/main/java/kiwi/orbit/compose/ui/controls/RadioField.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,19 @@ public fun RadioField(
end = contentPadding.calculateEndPadding(layoutDirection),
),
) {
val radioVerticalShift = 1.dp
Radio(
selected = selected,
onClick = null,
modifier = Modifier.padding(
top = (contentPadding.calculateTopPadding() - 3.dp).coerceAtLeast(0.dp),
top = (contentPadding.calculateTopPadding() - radioVerticalShift).coerceAtLeast(0.dp),
end = 10.dp
),
enabled = enabled,
error = error,
interactionSource = interactionSource,
)
val topPadding = contentPadding.calculateTopPadding().coerceAtLeast(3.dp)
val topPadding = contentPadding.calculateTopPadding().coerceAtLeast(radioVerticalShift)
val bottomPadding = contentPadding.calculateBottomPadding()
Column(Modifier.padding(top = topPadding, bottom = bottomPadding)) {
CompositionLocalProvider(
Expand Down

0 comments on commit 27502be

Please sign in to comment.