Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 67 additions & 47 deletions app/src/main/java/to/bitkit/ui/components/Button.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import to.bitkit.ui.shared.modifiers.alphaFeedback
import to.bitkit.ui.shared.util.primaryButtonStyle
import to.bitkit.ui.theme.AppButtonDefaults
import to.bitkit.ui.theme.AppThemeSurface
Expand Down Expand Up @@ -72,57 +73,49 @@ fun PrimaryButton(
containerColor = Color.Transparent,
disabledContainerColor = Color.Transparent
),
contentPadding = PaddingValues(0.dp),
contentPadding = contentPadding,
shape = buttonShape,
modifier = Modifier
modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
.then(modifier)
.primaryButtonStyle(
isEnabled = enabled && !isLoading,
shape = buttonShape,
primaryColor = color
)
.alphaFeedback(enabled = enabled && !isLoading)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
.primaryButtonStyle(
isEnabled = enabled && !isLoading,
shape = buttonShape,
primaryColor = color
)
.padding(contentPadding)
) {
if (isLoading) {
CircularProgressIndicator(
color = Colors.White32,
strokeWidth = 2.dp,
modifier = Modifier.size(size.height / 2)
)
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
if (icon != null) {
Box(
modifier = if (enabled) {
Modifier
} else {
Modifier.graphicsLayer {
colorFilter = ColorFilter.tint(Colors.White32)
}
if (isLoading) {
CircularProgressIndicator(
color = Colors.White32,
strokeWidth = 2.dp,
modifier = Modifier.size(size.height / 2)
)
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
if (icon != null) {
Box(
modifier = if (enabled) {
Modifier
} else {
Modifier.graphicsLayer {
colorFilter = ColorFilter.tint(Colors.White32)
}
) {
icon()
}
}
text?.let {
Text(
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
) {
icon()
}
}
text?.let {
Text(
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
Expand All @@ -147,10 +140,9 @@ fun SecondaryButton(
colors = AppButtonDefaults.secondaryColors,
contentPadding = contentPadding,
border = border,
modifier = Modifier
modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
.then(modifier)
) {
if (isLoading) {
CircularProgressIndicator(
Expand Down Expand Up @@ -205,10 +197,9 @@ fun TertiaryButton(
enabled = enabled && !isLoading,
colors = AppButtonDefaults.tertiaryColors,
contentPadding = contentPadding,
modifier = Modifier
modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
.then(modifier)
) {
if (isLoading) {
CircularProgressIndicator(
Expand Down Expand Up @@ -258,6 +249,11 @@ private fun PrimaryButtonPreview() {
text = "Primary",
onClick = {},
)
PrimaryButton(
text = "Primary with padding",
modifier = Modifier.padding(horizontal = 32.dp),
onClick = {},
)
PrimaryButton(
text = "Primary With Icon",
onClick = {},
Expand Down Expand Up @@ -292,6 +288,25 @@ private fun PrimaryButtonPreview() {
size = ButtonSize.Small,
onClick = {},
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
PrimaryButton(
text = "Primary Small",
fullWidth = false,
size = ButtonSize.Small,
modifier = Modifier.weight(1f),
onClick = {},
)
PrimaryButton(
text = "Primary Small",
fullWidth = false,
size = ButtonSize.Small,
modifier = Modifier.weight(1f),
onClick = {},
)
}
PrimaryButton(
text = "Primary Small Color Not Full",
size = ButtonSize.Small,
Expand Down Expand Up @@ -360,6 +375,11 @@ private fun SecondaryButtonPreview() {
text = "Secondary",
onClick = {},
)
SecondaryButton(
text = "Secondary With padding",
modifier = Modifier.padding(horizontal = 32.dp),
onClick = {},
)
SecondaryButton(
text = "Secondary With Icon",
onClick = {},
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/to/bitkit/ui/shared/modifiers/ClickableAlpha.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ package to.bitkit.ui.shared.modifiers

import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
Expand Down Expand Up @@ -101,3 +106,33 @@ private class ClickableAlphaNode(
}
}
}

/**
* Applies alpha animation feedback on press without consuming click events.
* This allows the Button's onClick to work while providing full-area visual feedback.
*/
@Composable
fun Modifier.alphaFeedback(
pressedAlpha: Float = 0.7f,
enabled: Boolean = true,
): Modifier = if (enabled) {
val animatable = remember { Animatable(1f) }
val scope = rememberCoroutineScope()

this
.pointerInput(Unit) {
detectTapGestures(
onPress = {
scope.launch { animatable.animateTo(pressedAlpha) }
tryAwaitRelease()
scope.launch { animatable.animateTo(1f) }
},
onTap = null // Don't consume tap - let Button handle it
)
}
.graphicsLayer {
alpha = animatable.value
}
} else {
this
}
Loading