From 39e065733d0d17d71ec024c305445e2cdfc6bd45 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Sat, 11 Nov 2023 00:26:19 +0800 Subject: [PATCH] feat: add RadioButton --- .../flexiui/component/RadioButton.kt | 148 +++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt index 2cfbfd9..9020bfe 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt @@ -23,4 +23,150 @@ package com.highcapable.flexiui.component -// TODO: To be implemented \ No newline at end of file +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.highcapable.flexiui.LocalColors +import com.highcapable.flexiui.LocalSizes +import com.highcapable.flexiui.interaction.clickable + +@Immutable +data class RadioButtonColors( + val contentColor: Color, + val inactiveColor: Color, + val activeColor: Color +) + +@Immutable +data class RadioButtonStyle( + val contentSize: Dp, + val contentShadowSize: Dp, + val strokeSize: Dp, + val pressedGain: Float, + val hoveredGain: Float, + val shape: Shape, + val border: BorderStroke +) + +@Composable +fun RadioButton( + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: RadioButtonColors = RadioButton.colors, + style: RadioButtonStyle = RadioButton.style, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + contentSpacing: Dp = RadioButton.contentSpacing, + content: @Composable () -> Unit = {} +) { + val hovered by interactionSource.collectIsHoveredAsState() + val pressed by interactionSource.collectIsPressedAsState() + val animatedStrokeScale by animateFloatAsState(if (pressed) style.pressedGain else 1f) + val animatedColor by animateColorAsState(if (selected) colors.activeColor else colors.inactiveColor) + val animatedContentScale by animateFloatAsState(if (hovered) style.hoveredGain else 1f) + val animatedContentShadow by animateDpAsState(if (selected) style.contentShadowSize else 0.dp) + val animatedContentAlpha by animateFloatAsState(if (selected) 1f else 0f) + val sModifier = if (enabled) modifier else modifier.alpha(0.5f) + Row(modifier = sModifier, verticalAlignment = Alignment.CenterVertically) { + Box( + modifier = Modifier.clickable( + interactionSource = interactionSource, + enabled = enabled, + role = Role.RadioButton, + onClick = onClick + ).size(style.strokeSize) + .scale(animatedStrokeScale) + .background(animatedColor, style.shape), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier.size(style.contentSize) + .scale(animatedContentScale) + .shadow(animatedContentShadow, style.shape) + .alpha(animatedContentAlpha) + .background(colors.contentColor, style.shape) + ) + } + Box( + modifier = Modifier.padding(start = contentSpacing) + .clickable(enabled = enabled, onClick = onClick) + ) { content() } + } +} + +object RadioButton { + val colors: RadioButtonColors + @Composable + @ReadOnlyComposable + get() = defaultRadioButtonColors() + val style: RadioButtonStyle + @Composable + @ReadOnlyComposable + get() = defaultRadioButtonStyle() + val contentSpacing: Dp + @Composable + @ReadOnlyComposable + get() = defaultRadioButtonContentSpacing() +} + +@Composable +@ReadOnlyComposable +private fun defaultRadioButtonColors() = RadioButtonColors( + contentColor = Color.White, + inactiveColor = LocalColors.current.themeTertiary, + activeColor = LocalColors.current.themePrimary +) + +@Composable +@ReadOnlyComposable +private fun defaultRadioButtonStyle() = RadioButtonStyle( + contentSize = DefaultContentSize, + contentShadowSize = DefaultContentShadowSize, + strokeSize = DefaultStrokeSize, + pressedGain = DefaultPressedGain, + hoveredGain = DefaultHoveredGain, + shape = CircleShape, + border = defaultRadioButtonBorder() +) + +@Composable +@ReadOnlyComposable +private fun defaultRadioButtonBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary) + +@Composable +@ReadOnlyComposable +private fun defaultRadioButtonContentSpacing() = LocalSizes.current.spacingSecondary + +private val DefaultContentSize = 10.dp +private val DefaultStrokeSize = 20.dp + +private const val DefaultPressedGain = 0.9f +private const val DefaultHoveredGain = 1.2f + +private val DefaultContentShadowSize = 0.5.dp \ No newline at end of file