From 994947a1a2f1b4a44543a993472f51b0a189ffe8 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 09:49:14 -0300 Subject: [PATCH 01/14] feat: create glassBlur modifier --- .../to/bitkit/ui/shared/util/Modifiers.kt | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index 709fc5694..f2ea6f8b8 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -154,6 +154,86 @@ private class OuterGlowNode( } } +fun Modifier.glassBlur( + blurRadius: Dp = 8.dp, + tintColor: Color = Color.White, + tintAlpha: Float = 0.05f, + cornerRadius: Dp = 64.dp, +): Modifier = this.then( + GlassBlurElement( + blurRadius = blurRadius, + tintColor = tintColor, + tintAlpha = tintAlpha, + cornerRadius = cornerRadius, + ) +) + +private data class GlassBlurElement( + val blurRadius: Dp, + val tintColor: Color, + val tintAlpha: Float, + val cornerRadius: Dp, +) : ModifierNodeElement() { + override fun create(): GlassBlurNode = GlassBlurNode( + blurRadius = blurRadius, + tintColor = tintColor, + tintAlpha = tintAlpha, + cornerRadius = cornerRadius, + ) + + override fun update(node: GlassBlurNode) { + node.blurRadius = blurRadius + node.tintColor = tintColor + node.tintAlpha = tintAlpha + node.cornerRadius = cornerRadius + } + + override fun InspectorInfo.inspectableProperties() { + name = "glassBlur" + properties["blurRadius"] = blurRadius + properties["tintColor"] = tintColor + properties["tintAlpha"] = tintAlpha + properties["cornerRadius"] = cornerRadius + } +} + +private class GlassBlurNode( + var blurRadius: Dp, + var tintColor: Color, + var tintAlpha: Float, + var cornerRadius: Dp, +) : DrawModifierNode, Modifier.Node() { + override fun ContentDrawScope.draw() { + val blurRadiusPx = blurRadius.toPx() + val cornerRadiusPx = cornerRadius.toPx() + + drawIntoCanvas { canvas -> + val paint = Paint().apply { + isAntiAlias = true + } + + val frameworkPaint = paint.asFrameworkPaint() + frameworkPaint.color = tintColor.copy(alpha = tintAlpha).toArgb() + frameworkPaint.maskFilter = android.graphics.BlurMaskFilter( + blurRadiusPx, + android.graphics.BlurMaskFilter.Blur.NORMAL, + ) + + canvas.drawRoundRect( + left = 0f, + top = 0f, + right = size.width, + bottom = size.height, + radiusX = cornerRadiusPx, + radiusY = cornerRadiusPx, + paint = paint, + ) + } + + drawContent() + } +} + fun Modifier.primaryButtonStyle( isEnabled: Boolean, shape: Shape, From 2e78a6b37822f508630d5a43f3bc2d2f778c7555 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 09:50:31 -0300 Subject: [PATCH 02/14] feat: simulate blur effect and match figma attributes for secondary button --- .../java/to/bitkit/ui/components/Button.kt | 184 +++++++++++------- .../main/java/to/bitkit/ui/theme/Defaults.kt | 1 + 2 files changed, 112 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index f64ef19db..e52796627 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -3,6 +3,7 @@ package to.bitkit.ui.components import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -28,12 +29,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource 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.R import to.bitkit.ui.shared.modifiers.alphaFeedback import to.bitkit.ui.shared.modifiers.rememberDebouncedClick +import to.bitkit.ui.shared.util.glassBlur import to.bitkit.ui.shared.util.primaryButtonStyle import to.bitkit.ui.theme.AppButtonDefaults import to.bitkit.ui.theme.AppThemeSurface @@ -52,6 +57,21 @@ enum class ButtonSize { Small -> 16.dp Large -> 24.dp } + val secondaryHorizontalPadding: Dp + get() = when (this) { + Small -> 16.dp + Large -> 28.dp + } + val secondaryGap: Dp + get() = when (this) { + Small -> 8.dp + Large -> 6.dp + } + val secondaryBlurRadius: Dp + get() = when (this) { + Small -> 5.dp + Large -> 8.dp + } } @Composable @@ -126,6 +146,11 @@ fun PrimaryButton( } } +private fun secondaryBorder(size: ButtonSize, enabled: Boolean) = when (size) { + ButtonSize.Large -> BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent) + ButtonSize.Small -> BorderStroke(1.dp, if (enabled) Colors.White16 else Color.Transparent) +} + @Composable fun SecondaryButton( text: String?, @@ -137,17 +162,22 @@ fun SecondaryButton( enabled: Boolean = true, fullWidth: Boolean = true, ) { - val contentPadding = PaddingValues(horizontal = size.horizontalPadding.takeIf { text != null } ?: 0.dp) - val border = BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent) + val contentPadding = PaddingValues(horizontal = size.secondaryHorizontalPadding.takeIf { text != null } ?: 0.dp) + val border = secondaryBorder(size, enabled) + val contentColor = when (size) { + ButtonSize.Large -> Colors.White80 + ButtonSize.Small -> Colors.White64 + } OutlinedButton( onClick = rememberDebouncedClick(onClick = onClick), enabled = enabled && !isLoading, - colors = AppButtonDefaults.secondaryColors, + colors = AppButtonDefaults.secondaryColors.copy(contentColor = contentColor), contentPadding = contentPadding, border = border, modifier = modifier .then(if (fullWidth) Modifier.fillMaxWidth() else Modifier) .requiredHeight(size.height) + .glassBlur(blurRadius = size.secondaryBlurRadius) ) { if (isLoading) { CircularProgressIndicator( @@ -158,7 +188,7 @@ fun SecondaryButton( } else { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(size.secondaryGap), ) { if (icon != null) { Box( @@ -373,75 +403,29 @@ private fun PrimaryButtonPreview() { @Composable private fun SecondaryButtonPreview() { AppThemeSurface { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(16.dp) - ) { - SecondaryButton( - text = "Secondary", - onClick = {}, - ) - SecondaryButton( - text = "Secondary With padding", - modifier = Modifier.padding(horizontal = 32.dp), - onClick = {}, - ) - SecondaryButton( - text = "Secondary With Icon", - onClick = {}, - icon = { - Icon( - imageVector = Icons.Filled.Favorite, - contentDescription = "", - modifier = Modifier.size(16.dp) - ) - }, - ) - SecondaryButton( - text = "Secondary Loading", - isLoading = true, - onClick = {}, - ) - SecondaryButton( - text = "Secondary Disabled", - onClick = {}, - enabled = false, - icon = { - Icon( - imageVector = Icons.Filled.Favorite, - contentDescription = "", - modifier = Modifier.size(16.dp) - ) - }, - ) - SecondaryButton( - text = "Secondary Small", - size = ButtonSize.Small, - fullWidth = false, - onClick = {}, + Box { + Image( + painter = painterResource(R.drawable.lightning), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.matchParentSize() ) - SecondaryButton( - text = "Secondary Small Loading", - size = ButtonSize.Small, - isLoading = true, - onClick = {}, - ) - SecondaryButton( - text = "Secondary Small Disabled", - size = ButtonSize.Small, - onClick = {}, - enabled = false, - ) - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(16.dp) ) { SecondaryButton( - text = null, + text = "Secondary", + onClick = {}, + ) + SecondaryButton( + text = "Secondary With padding", + modifier = Modifier.padding(horizontal = 32.dp), + onClick = {}, + ) + SecondaryButton( + text = "Secondary With Icon", onClick = {}, - fullWidth = false, - size = ButtonSize.Large, icon = { Icon( imageVector = Icons.Filled.Favorite, @@ -451,19 +435,73 @@ private fun SecondaryButtonPreview() { }, ) SecondaryButton( - text = null, + text = "Secondary Loading", + isLoading = true, + onClick = {}, + ) + SecondaryButton( + text = "Secondary Disabled", onClick = {}, - fullWidth = false, - size = ButtonSize.Small, enabled = false, icon = { Icon( - imageVector = Icons.Filled.Home, + imageVector = Icons.Filled.Favorite, contentDescription = "", modifier = Modifier.size(16.dp) ) }, ) + SecondaryButton( + text = "Secondary Small", + size = ButtonSize.Small, + fullWidth = false, + onClick = {}, + ) + SecondaryButton( + text = "Secondary Small Loading", + size = ButtonSize.Small, + isLoading = true, + onClick = {}, + ) + SecondaryButton( + text = "Secondary Small Disabled", + size = ButtonSize.Small, + onClick = {}, + enabled = false, + ) + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + SecondaryButton( + text = null, + onClick = {}, + fullWidth = false, + size = ButtonSize.Large, + icon = { + Icon( + imageVector = Icons.Filled.Favorite, + contentDescription = "", + modifier = Modifier.size(16.dp) + ) + }, + ) + SecondaryButton( + text = null, + onClick = {}, + fullWidth = false, + size = ButtonSize.Small, + enabled = false, + icon = { + Icon( + imageVector = Icons.Filled.Home, + contentDescription = "", + modifier = Modifier.size(16.dp) + ) + }, + ) + } } } } diff --git a/app/src/main/java/to/bitkit/ui/theme/Defaults.kt b/app/src/main/java/to/bitkit/ui/theme/Defaults.kt index d7252c73e..1c7d71b12 100644 --- a/app/src/main/java/to/bitkit/ui/theme/Defaults.kt +++ b/app/src/main/java/to/bitkit/ui/theme/Defaults.kt @@ -66,6 +66,7 @@ object AppButtonDefaults { val secondaryColors: ButtonColors @Composable get() = ButtonDefaults.outlinedButtonColors( + containerColor = Colors.White.copy(alpha = 0.01f), contentColor = Colors.White80, disabledContentColor = Colors.White32, ) From 68592075aabe6dceaa9a05ce658b9df8c6662422 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 10:09:34 -0300 Subject: [PATCH 03/14] fix: primary button attributes WIP --- .../java/to/bitkit/ui/components/Button.kt | 24 ++++++++++++++--- .../to/bitkit/ui/shared/util/Modifiers.kt | 26 +++++++------------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index e52796627..577136b30 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -57,6 +57,21 @@ enum class ButtonSize { Small -> 16.dp Large -> 24.dp } + val primaryHorizontalPadding: Dp + get() = when (this) { + Small -> 16.dp + Large -> 32.dp + } + val primaryGap: Dp + get() = when (this) { + Small -> 8.dp + Large -> 6.dp + } + val primaryShadowElevation: Dp + get() = when (this) { + Small -> 4.dp + Large -> 16.dp + } val secondaryHorizontalPadding: Dp get() = when (this) { Small -> 16.dp @@ -87,8 +102,8 @@ fun PrimaryButton( color: Color? = null, enableGradient: Boolean = true, ) { - val contentPadding = PaddingValues(horizontal = size.horizontalPadding.takeIf { text != null } ?: 0.dp) - val buttonShape = MaterialTheme.shapes.large + val contentPadding = PaddingValues(horizontal = size.primaryHorizontalPadding.takeIf { text != null } ?: 0.dp) + val buttonShape = MaterialTheme.shapes.extraLarge Button( onClick = rememberDebouncedClick(onClick = onClick), @@ -106,7 +121,8 @@ fun PrimaryButton( isEnabled = enabled && !isLoading, shape = buttonShape, primaryColor = color, - enableGradient = enableGradient + enableGradient = enableGradient, + shadowElevation = size.primaryShadowElevation, ) .alphaFeedback(enabled = enabled && !isLoading) ) { @@ -119,7 +135,7 @@ fun PrimaryButton( } else { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(size.primaryGap), ) { if (icon != null) { Box( diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index f2ea6f8b8..661554c5d 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -239,15 +239,16 @@ fun Modifier.primaryButtonStyle( shape: Shape, primaryColor: Color? = null, enableGradient: Boolean = true, + shadowElevation: Dp = 16.dp, ): Modifier { return this // Step 1: Add shadow (only when enabled) .then( if (isEnabled) { Modifier.shadow( - elevation = 16.dp, + elevation = shadowElevation, shape = shape, - clip = false // Don't clip content, just add shadow + clip = false, ) } else { Modifier @@ -266,32 +267,25 @@ fun Modifier.primaryButtonStyle( brush = Brush.verticalGradient( colors = listOf(baseColor, Colors.Gray6), startY = 0f, - endY = size.height + endY = size.height, ), topLeft = Offset.Zero, - size = size + size = size, ) } else { drawRect( color = baseColor, topLeft = Offset.Zero, - size = size + size = size, ) } - // Draw top border highlight (2dp gradient fade) - val borderHeight = 2.dp.toPx() + // Draw top border highlight (1dp solid White10) + val borderHeight = 1.dp.toPx() drawRect( - brush = Brush.verticalGradient( - colors = listOf( - Colors.White16, - Color.Transparent - ), - startY = 0f, - endY = borderHeight - ), + color = Colors.White10, topLeft = Offset(0f, 0f), - size = Size(size.width, borderHeight) + size = Size(size.width, borderHeight), ) // Draw the actual button content on top From 690378506b37f1f42de1ba22f53336c63edb1e40 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 10:19:46 -0300 Subject: [PATCH 04/14] fix: replace top shine rect with drawpath --- .../to/bitkit/ui/shared/util/Modifiers.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index 661554c5d..00a091f10 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -12,12 +12,14 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.addOutline import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.pointerInput @@ -280,12 +282,18 @@ fun Modifier.primaryButtonStyle( ) } - // Draw top border highlight (1dp solid White10) - val borderHeight = 1.dp.toPx() - drawRect( - color = Colors.White10, - topLeft = Offset(0f, 0f), - size = Size(size.width, borderHeight), + // Draw top shine highlight following the rounded contour + val outline = shape.createOutline(size, layoutDirection, this) + val shinePath = Path() + shinePath.addOutline(outline) + drawPath( + path = shinePath, + brush = Brush.verticalGradient( + colors = listOf(Colors.White10, Color.Transparent), + startY = 0f, + endY = size.height * 0.15f, + ), + style = Stroke(width = 1.dp.toPx()), ) // Draw the actual button content on top From 26622ef6f3422f8e87db5ef89adc43b7bd261c5b Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 10:27:32 -0300 Subject: [PATCH 05/14] fix: render disable content on the same drawWithContent of enabled --- .../to/bitkit/ui/shared/util/Modifiers.kt | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index 00a091f10..bd84a3389 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -258,49 +258,49 @@ fun Modifier.primaryButtonStyle( ) // Step 2: Clip to shape first .clip(shape) - // Step 3: Apply gradient background with border overlay - .then( + // Step 3: Apply background with optional gradient and shine + .drawWithContent { if (isEnabled) { - Modifier.drawWithContent { - // Draw the main background filling entire button - val baseColor = primaryColor ?: Colors.Gray5 - if (enableGradient) { - drawRect( - brush = Brush.verticalGradient( - colors = listOf(baseColor, Colors.Gray6), - startY = 0f, - endY = size.height, - ), - topLeft = Offset.Zero, - size = size, - ) - } else { - drawRect( - color = baseColor, - topLeft = Offset.Zero, - size = size, - ) - } - - // Draw top shine highlight following the rounded contour - val outline = shape.createOutline(size, layoutDirection, this) - val shinePath = Path() - shinePath.addOutline(outline) - drawPath( - path = shinePath, + val baseColor = primaryColor ?: Colors.Gray5 + if (enableGradient) { + drawRect( brush = Brush.verticalGradient( - colors = listOf(Colors.White10, Color.Transparent), + colors = listOf(baseColor, Colors.Gray6), startY = 0f, - endY = size.height * 0.15f, + endY = size.height, ), - style = Stroke(width = 1.dp.toPx()), + topLeft = Offset.Zero, + size = size, + ) + } else { + drawRect( + color = baseColor, + topLeft = Offset.Zero, + size = size, ) - - // Draw the actual button content on top - drawContent() } + + // Draw top shine highlight following the rounded contour + val outline = shape.createOutline(size, layoutDirection, this) + val shinePath = Path() + shinePath.addOutline(outline) + drawPath( + path = shinePath, + brush = Brush.verticalGradient( + colors = listOf(Colors.White10, Color.Transparent), + startY = 0f, + endY = size.height * 0.15f, + ), + style = Stroke(width = 1.dp.toPx()), + ) } else { - Modifier.background(Colors.White06) + drawRect( + color = Colors.White06, + topLeft = Offset.Zero, + size = size, + ) } - ) + + drawContent() + } } From c63f993bc52a444808c210b22e44731b08984e7a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 11:42:45 -0300 Subject: [PATCH 06/14] fix: match tertiary button style with figma --- .../java/to/bitkit/ui/components/Button.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index 577136b30..b7a426719 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -22,7 +22,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -37,6 +36,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.shared.modifiers.alphaFeedback +import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.shared.modifiers.rememberDebouncedClick import to.bitkit.ui.shared.util.glassBlur import to.bitkit.ui.shared.util.primaryButtonStyle @@ -242,15 +242,17 @@ fun TertiaryButton( enabled: Boolean = true, fullWidth: Boolean = true, ) { - val contentPadding = PaddingValues(horizontal = size.horizontalPadding.takeIf { text != null } ?: 0.dp) - TextButton( - onClick = rememberDebouncedClick(onClick = onClick), - enabled = enabled && !isLoading, - colors = AppButtonDefaults.tertiaryColors, - contentPadding = contentPadding, + val contentColor = if (enabled && !isLoading) Colors.White80 else Colors.White32 + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, modifier = modifier .then(if (fullWidth) Modifier.fillMaxWidth() else Modifier) .requiredHeight(size.height) + .clickableAlpha( + enabled = enabled && !isLoading, + onClick = onClick, + ), ) { if (isLoading) { CircularProgressIndicator( @@ -279,6 +281,8 @@ fun TertiaryButton( text?.let { Text( text = text, + style = MaterialTheme.typography.labelLarge, + color = contentColor, maxLines = 1, overflow = TextOverflow.Ellipsis, ) From c95600c1a76379af4e74adfa7ece3243951365ca Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 11:50:45 -0300 Subject: [PATCH 07/14] refactor: tidy up code --- .../java/to/bitkit/ui/components/Button.kt | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index b7a426719..7ed0032d4 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -52,11 +52,6 @@ enum class ButtonSize { Small -> 40.dp Large -> 56.dp } - val horizontalPadding: Dp - get() = when (this) { - Small -> 16.dp - Large -> 24.dp - } val primaryHorizontalPadding: Dp get() = when (this) { Small -> 16.dp @@ -87,6 +82,11 @@ enum class ButtonSize { Small -> 5.dp Large -> 8.dp } + + fun secondaryBorder(enabled: Boolean): BorderStroke = when (this) { + Large -> BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent) + Small -> BorderStroke(1.dp, if (enabled) Colors.White16 else Color.Transparent) + } } @Composable @@ -162,11 +162,6 @@ fun PrimaryButton( } } -private fun secondaryBorder(size: ButtonSize, enabled: Boolean) = when (size) { - ButtonSize.Large -> BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent) - ButtonSize.Small -> BorderStroke(1.dp, if (enabled) Colors.White16 else Color.Transparent) -} - @Composable fun SecondaryButton( text: String?, @@ -179,7 +174,7 @@ fun SecondaryButton( fullWidth: Boolean = true, ) { val contentPadding = PaddingValues(horizontal = size.secondaryHorizontalPadding.takeIf { text != null } ?: 0.dp) - val border = secondaryBorder(size, enabled) + val border = size.secondaryBorder(enabled) val contentColor = when (size) { ButtonSize.Large -> Colors.White80 ButtonSize.Small -> Colors.White64 @@ -245,7 +240,7 @@ fun TertiaryButton( val contentColor = if (enabled && !isLoading) Colors.White80 else Colors.White32 Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), modifier = modifier .then(if (fullWidth) Modifier.fillMaxWidth() else Modifier) .requiredHeight(size.height) @@ -261,32 +256,27 @@ fun TertiaryButton( 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 (icon != null) { + Box( + modifier = if (enabled) { + Modifier + } else { + Modifier.graphicsLayer { + colorFilter = ColorFilter.tint(Colors.White32) } - ) { - icon() } + ) { + icon() } - text?.let { - Text( - text = text, - style = MaterialTheme.typography.labelLarge, - color = contentColor, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } + } + text?.let { + Text( + text = text, + style = MaterialTheme.typography.labelLarge, + color = contentColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) } } } From 82338dc4bc47244e6d74b0ca836a9b1470c10d77 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 11:53:49 -0300 Subject: [PATCH 08/14] feat: implement GradientCircularProgressIndicator --- app/src/main/java/to/bitkit/ui/components/Button.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index 7ed0032d4..49cc8958f 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -17,7 +17,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home import androidx.compose.material3.Button -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton @@ -127,8 +126,7 @@ fun PrimaryButton( .alphaFeedback(enabled = enabled && !isLoading) ) { if (isLoading) { - CircularProgressIndicator( - color = Colors.White32, + GradientCircularProgressIndicator( strokeWidth = 2.dp, modifier = Modifier.size(size.height / 2) ) @@ -191,8 +189,7 @@ fun SecondaryButton( .glassBlur(blurRadius = size.secondaryBlurRadius) ) { if (isLoading) { - CircularProgressIndicator( - color = Colors.White32, + GradientCircularProgressIndicator( strokeWidth = 2.dp, modifier = Modifier.size(size.height / 2) ) @@ -250,8 +247,7 @@ fun TertiaryButton( ), ) { if (isLoading) { - CircularProgressIndicator( - color = Colors.White32, + GradientCircularProgressIndicator( strokeWidth = 2.dp, modifier = Modifier.size(size.height / 2) ) From f127433dcd6671de609a7e313b3f684c28196f0f Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 13:18:29 -0300 Subject: [PATCH 09/14] chore: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b6d4bbe..6654d1638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed +- Polish Primary, Secondary, and Tertiary buttons to match Figma design specs #887 - Fix ANR on RGS server settings screen caused by catastrophic regex backtracking #880 - Fix crash when returning app to foreground on Receive screen #875 - Show loading state on Spending tab when node is not running #875 From 6224d0c4417216ec63372184a1f2432d63795380 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 3 Apr 2026 08:49:51 -0300 Subject: [PATCH 10/14] fix: remove white color BG --- .../to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt index 76d7ffb8a..7d10de0c3 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt @@ -376,7 +376,6 @@ private fun ReceiveQrView( size = ButtonSize.Small, onClick = onClickEditInvoice, fullWidth = false, - color = Colors.White10, icon = { Icon( painter = painterResource(R.drawable.ic_pencil_simple), @@ -402,7 +401,6 @@ private fun ReceiveQrView( coroutineScope.launch { qrButtonTooltipState.show() } }, fullWidth = true, - color = Colors.White10, icon = { Icon( painter = painterResource(R.drawable.ic_copy), @@ -425,7 +423,6 @@ private fun ReceiveQrView( } ?: shareText(context, copyText) }, fullWidth = false, - color = Colors.White10, icon = { Icon( painter = painterResource(R.drawable.ic_share), @@ -594,7 +591,6 @@ private fun CopyAddressCard( size = ButtonSize.Small, onClick = onClickEditInvoice, fullWidth = false, - color = Colors.White10, icon = { Icon( painter = painterResource(R.drawable.ic_pencil_simple), @@ -620,7 +616,6 @@ private fun CopyAddressCard( coroutineScope.launch { tooltipState.show() } }, fullWidth = false, - color = Colors.White10, icon = { Icon( painter = painterResource(R.drawable.ic_copy), @@ -637,7 +632,6 @@ private fun CopyAddressCard( size = ButtonSize.Small, onClick = { shareText(context, address) }, fullWidth = false, - color = Colors.White10, icon = { Icon( painter = painterResource(R.drawable.ic_share), From 95c169ef2560db81aa42750cee86511305082481 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 6 Apr 2026 02:37:09 +0200 Subject: [PATCH 11/14] fix: use haze for secondary button bg blur --- app/src/main/java/to/bitkit/di/HttpModule.kt | 2 +- app/src/main/java/to/bitkit/ui/Locals.kt | 5 + .../java/to/bitkit/ui/components/Button.kt | 140 +++++++++++------- .../ui/screens/wallets/SavingsWalletScreen.kt | 43 ++++-- .../screens/wallets/SpendingWalletScreen.kt | 41 +++-- .../to/bitkit/ui/shared/util/Modifiers.kt | 80 ---------- .../java/to/bitkit/viewmodels/AppViewModel.kt | 2 +- 7 files changed, 155 insertions(+), 158 deletions(-) diff --git a/app/src/main/java/to/bitkit/di/HttpModule.kt b/app/src/main/java/to/bitkit/di/HttpModule.kt index f652939b3..4a1f551c6 100644 --- a/app/src/main/java/to/bitkit/di/HttpModule.kt +++ b/app/src/main/java/to/bitkit/di/HttpModule.kt @@ -18,9 +18,9 @@ import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json -import to.bitkit.utils.UrlValidator import to.bitkit.utils.AppError import to.bitkit.utils.Logger +import to.bitkit.utils.UrlValidator import javax.inject.Singleton import io.ktor.client.plugins.logging.Logger as KtorLogger diff --git a/app/src/main/java/to/bitkit/ui/Locals.kt b/app/src/main/java/to/bitkit/ui/Locals.kt index 2843e38c4..b766d86af 100644 --- a/app/src/main/java/to/bitkit/ui/Locals.kt +++ b/app/src/main/java/to/bitkit/ui/Locals.kt @@ -4,6 +4,7 @@ import androidx.compose.material3.DrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf +import dev.chrisbanes.haze.HazeState import to.bitkit.models.BalanceState import to.bitkit.repositories.CurrencyState import to.bitkit.viewmodels.ActivityListViewModel @@ -29,6 +30,7 @@ val LocalActivityListViewModel = staticCompositionLocalOf { null } val LocalSettingsViewModel = staticCompositionLocalOf { null } val LocalBackupsViewModel = staticCompositionLocalOf { null } +val LocalHazeState = staticCompositionLocalOf { null } val appViewModel: AppViewModel? @Composable get() = LocalAppViewModel.current @@ -56,3 +58,6 @@ val backupsViewModel: BackupsViewModel? val drawerState: DrawerState? @Composable get() = LocalDrawerState.current + +val hazeState: HazeState? + @Composable get() = LocalHazeState.current diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index 49cc8958f..d15c177c7 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight @@ -24,6 +25,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.graphicsLayer @@ -33,11 +35,16 @@ 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 dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.HazeStyle +import dev.chrisbanes.haze.HazeTint +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.rememberHazeState import to.bitkit.R import to.bitkit.ui.shared.modifiers.alphaFeedback import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.shared.modifiers.rememberDebouncedClick -import to.bitkit.ui.shared.util.glassBlur import to.bitkit.ui.shared.util.primaryButtonStyle import to.bitkit.ui.theme.AppButtonDefaults import to.bitkit.ui.theme.AppThemeSurface @@ -76,12 +83,6 @@ enum class ButtonSize { Small -> 8.dp Large -> 6.dp } - val secondaryBlurRadius: Dp - get() = when (this) { - Small -> 5.dp - Large -> 8.dp - } - fun secondaryBorder(enabled: Boolean): BorderStroke = when (this) { Large -> BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent) Small -> BorderStroke(1.dp, if (enabled) Colors.White16 else Color.Transparent) @@ -170,6 +171,7 @@ fun SecondaryButton( size: ButtonSize = ButtonSize.Large, enabled: Boolean = true, fullWidth: Boolean = true, + hazeState: HazeState? = null, ) { val contentPadding = PaddingValues(horizontal = size.secondaryHorizontalPadding.takeIf { text != null } ?: 0.dp) val border = size.secondaryBorder(enabled) @@ -177,46 +179,67 @@ fun SecondaryButton( ButtonSize.Large -> Colors.White80 ButtonSize.Small -> Colors.White64 } - OutlinedButton( - onClick = rememberDebouncedClick(onClick = onClick), - enabled = enabled && !isLoading, - colors = AppButtonDefaults.secondaryColors.copy(contentColor = contentColor), - contentPadding = contentPadding, - border = border, + // hazeEffect must be on a Box wrapper (not OutlinedButton — Material's Surface draws over it) + // and AFTER size modifiers (Haze needs to know dimensions) + val buttonShape = MaterialTheme.shapes.extraLarge + Box( modifier = modifier .then(if (fullWidth) Modifier.fillMaxWidth() else Modifier) .requiredHeight(size.height) - .glassBlur(blurRadius = size.secondaryBlurRadius) - ) { - if (isLoading) { - GradientCircularProgressIndicator( - strokeWidth = 2.dp, - modifier = Modifier.size(size.height / 2) + .clip(buttonShape) + .then( + if (hazeState != null) { + Modifier.hazeEffect( + state = hazeState, + style = HazeStyle( + blurRadius = 12.dp, + backgroundColor = Color.Black, + tint = HazeTint(Color.Black.copy(alpha = 0.2f)), + ), + ) + } else { + Modifier + } ) - } else { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(size.secondaryGap), - ) { - if (icon != null) { - Box( - modifier = if (enabled) { - Modifier - } else { - Modifier.graphicsLayer { - colorFilter = ColorFilter.tint(Colors.White32) + ) { + OutlinedButton( + onClick = rememberDebouncedClick(onClick = onClick), + enabled = enabled && !isLoading, + colors = AppButtonDefaults.secondaryColors.copy(contentColor = contentColor), + contentPadding = contentPadding, + border = border, + modifier = if (fullWidth) Modifier.fillMaxSize() else Modifier, + ) { + if (isLoading) { + GradientCircularProgressIndicator( + strokeWidth = 2.dp, + modifier = Modifier.size(size.height / 2) + ) + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(size.secondaryGap), + ) { + if (icon != null) { + Box( + modifier = if (enabled) { + Modifier + } else { + Modifier.graphicsLayer { + colorFilter = ColorFilter.tint(Colors.White32) + } } + ) { + icon() } - ) { - icon() } - } - text?.let { - Text( - text = text, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) + text?.let { + Text( + text = text, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } } } } @@ -278,7 +301,7 @@ fun TertiaryButton( } } -@Preview(showBackground = true) +@Preview @Composable private fun PrimaryButtonPreview() { AppThemeSurface { @@ -405,33 +428,39 @@ private fun PrimaryButtonPreview() { } } -@Preview(showBackground = true) +@Preview @Composable private fun SecondaryButtonPreview() { + val hazeState = rememberHazeState() AppThemeSurface { Box { - Image( - painter = painterResource(R.drawable.lightning), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier.matchParentSize() - ) + Box( + modifier = Modifier + .matchParentSize() + .hazeSource(hazeState) + ) { + Image( + painter = painterResource(R.drawable.lightning), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.matchParentSize() + ) + } Column( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp) ) { - SecondaryButton( - text = "Secondary", - onClick = {}, - ) + SecondaryButton(text = "Secondary", hazeState = hazeState, onClick = {}) SecondaryButton( text = "Secondary With padding", modifier = Modifier.padding(horizontal = 32.dp), + hazeState = hazeState, onClick = {}, ) SecondaryButton( text = "Secondary With Icon", onClick = {}, + hazeState = hazeState, icon = { Icon( imageVector = Icons.Filled.Favorite, @@ -443,12 +472,14 @@ private fun SecondaryButtonPreview() { SecondaryButton( text = "Secondary Loading", isLoading = true, + hazeState = hazeState, onClick = {}, ) SecondaryButton( text = "Secondary Disabled", onClick = {}, enabled = false, + hazeState = hazeState, icon = { Icon( imageVector = Icons.Filled.Favorite, @@ -461,12 +492,14 @@ private fun SecondaryButtonPreview() { text = "Secondary Small", size = ButtonSize.Small, fullWidth = false, + hazeState = hazeState, onClick = {}, ) SecondaryButton( text = "Secondary Small Loading", size = ButtonSize.Small, isLoading = true, + hazeState = hazeState, onClick = {}, ) SecondaryButton( @@ -485,6 +518,7 @@ private fun SecondaryButtonPreview() { onClick = {}, fullWidth = false, size = ButtonSize.Large, + hazeState = hazeState, icon = { Icon( imageVector = Icons.Filled.Favorite, @@ -513,7 +547,7 @@ private fun SecondaryButtonPreview() { } } -@Preview(showBackground = true) +@Preview @Composable private fun TertiaryButtonPreview() { AppThemeSurface { diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index 649f4732b..95720d597 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -5,13 +5,19 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -26,6 +32,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.synonym.bitkitcore.Activity +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.rememberHazeState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import to.bitkit.R @@ -68,22 +76,32 @@ fun SavingsWalletScreen( mutableStateOf(hasFunds && !isGeoBlocked) } + val hazeState = rememberHazeState() Box( modifier = Modifier .fillMaxSize() - .background(Colors.Black) .blockPointerInputPassthrough() ) { - Image( - painter = painterResource(id = R.drawable.piggybank), - contentDescription = null, - contentScale = ContentScale.Fit, + // Background layer: hazeSource must be a sibling of hazeEffect, not a parent. + // Haze can't blur an ancestor — source and effect must be at the same level. + Box( modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = 32.dp) - .offset(x = (120).dp) - .size(268.dp) - ) + .matchParentSize() + .background(Colors.Black) + .hazeSource(hazeState) + ) { + Image( + painter = painterResource(id = R.drawable.piggybank), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 0.dp) + .offset(x = (160).dp) + .size(360.dp) + .windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical)) + ) + } ScreenColumn(noBackground = true) { AppTopBar( titleText = stringResource(R.string.wallet__savings__title), @@ -108,7 +126,7 @@ fun SavingsWalletScreen( IncomingTransfer( amount = balances.balanceInTransferToSavings, remainingDuration = forceCloseRemainingDuration, - modifier = Modifier.padding(vertical = 8.dp), + modifier = Modifier.padding(vertical = 8.dp) ) } @@ -123,9 +141,10 @@ fun SavingsWalletScreen( Icon( painter = painterResource(R.drawable.ic_transfer), contentDescription = null, - modifier = Modifier.size(16.dp), + modifier = Modifier.size(16.dp) ) }, + hazeState = hazeState, modifier = Modifier.testTag("TransferToSpending") ) } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt index b10954e85..364abb8cf 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt @@ -5,13 +5,18 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -26,6 +31,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.synonym.bitkitcore.Activity +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.rememberHazeState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import org.lightningdevkit.ldknode.ChannelDetails @@ -70,21 +77,31 @@ fun SpendingWalletScreen( mutableStateOf(hasLnBalance && hasChannels) } + val hazeState = rememberHazeState() Box( modifier = Modifier .fillMaxSize() - .background(Colors.Black) .blockPointerInputPassthrough() ) { - Image( - painter = painterResource(id = R.drawable.coin_stack_x_2), - contentDescription = null, - contentScale = ContentScale.Fit, + // Background layer: hazeSource must be a sibling of hazeEffect, not a parent. + // Haze can't blur an ancestor — source and effect must be at the same level. + Box( modifier = Modifier - .align(Alignment.TopEnd) - .offset(x = (155).dp) - .size(330.dp) - ) + .matchParentSize() + .background(Colors.Black) + .hazeSource(hazeState) + ) { + Image( + painter = painterResource(id = R.drawable.coin_stack_x_2), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .align(Alignment.TopEnd) + .offset(x = (155).dp) + .size(330.dp) + .windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical)) + ) + } ScreenColumn(noBackground = true) { AppTopBar( titleText = stringResource(R.string.wallet__spending__title), @@ -123,9 +140,10 @@ fun SpendingWalletScreen( Icon( painter = painterResource(R.drawable.ic_transfer), contentDescription = null, - modifier = Modifier.size(16.dp), + modifier = Modifier.size(16.dp) ) }, + hazeState = hazeState, modifier = Modifier.testTag("TransferToSavings") ) } @@ -142,7 +160,8 @@ fun SpendingWalletScreen( } if (showEmptyState) { EmptyStateView( - text = stringResource(R.string.wallet__spending__onboarding).withAccent(accentColor = Colors.Purple), + text = stringResource(R.string.wallet__spending__onboarding) + .withAccent(accentColor = Colors.Purple), modifier = Modifier .systemBarsPadding() .align(Alignment.BottomCenter) diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index bd84a3389..e50243e66 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -156,86 +156,6 @@ private class OuterGlowNode( } } -fun Modifier.glassBlur( - blurRadius: Dp = 8.dp, - tintColor: Color = Color.White, - tintAlpha: Float = 0.05f, - cornerRadius: Dp = 64.dp, -): Modifier = this.then( - GlassBlurElement( - blurRadius = blurRadius, - tintColor = tintColor, - tintAlpha = tintAlpha, - cornerRadius = cornerRadius, - ) -) - -private data class GlassBlurElement( - val blurRadius: Dp, - val tintColor: Color, - val tintAlpha: Float, - val cornerRadius: Dp, -) : ModifierNodeElement() { - override fun create(): GlassBlurNode = GlassBlurNode( - blurRadius = blurRadius, - tintColor = tintColor, - tintAlpha = tintAlpha, - cornerRadius = cornerRadius, - ) - - override fun update(node: GlassBlurNode) { - node.blurRadius = blurRadius - node.tintColor = tintColor - node.tintAlpha = tintAlpha - node.cornerRadius = cornerRadius - } - - override fun InspectorInfo.inspectableProperties() { - name = "glassBlur" - properties["blurRadius"] = blurRadius - properties["tintColor"] = tintColor - properties["tintAlpha"] = tintAlpha - properties["cornerRadius"] = cornerRadius - } -} - -private class GlassBlurNode( - var blurRadius: Dp, - var tintColor: Color, - var tintAlpha: Float, - var cornerRadius: Dp, -) : DrawModifierNode, Modifier.Node() { - override fun ContentDrawScope.draw() { - val blurRadiusPx = blurRadius.toPx() - val cornerRadiusPx = cornerRadius.toPx() - - drawIntoCanvas { canvas -> - val paint = Paint().apply { - isAntiAlias = true - } - - val frameworkPaint = paint.asFrameworkPaint() - frameworkPaint.color = tintColor.copy(alpha = tintAlpha).toArgb() - frameworkPaint.maskFilter = android.graphics.BlurMaskFilter( - blurRadiusPx, - android.graphics.BlurMaskFilter.Blur.NORMAL, - ) - - canvas.drawRoundRect( - left = 0f, - top = 0f, - right = size.width, - bottom = size.height, - radiusX = cornerRadiusPx, - radiusY = cornerRadiusPx, - paint = paint, - ) - } - - drawContent() - } -} - fun Modifier.primaryButtonStyle( isEnabled: Boolean, shape: Shape, diff --git a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt index 392c4b5e1..2eeed6077 100644 --- a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt @@ -73,11 +73,11 @@ import to.bitkit.env.Env import to.bitkit.ext.WatchResult import to.bitkit.ext.amountOnClose import to.bitkit.ext.amountSats +import to.bitkit.ext.callbackAmountMsats import to.bitkit.ext.channelId import to.bitkit.ext.claimableAtHeight import to.bitkit.ext.getClipboardText import to.bitkit.ext.getSatsPerVByteFor -import to.bitkit.ext.callbackAmountMsats import to.bitkit.ext.isFixedAmount import to.bitkit.ext.maxSendableSat import to.bitkit.ext.maxWithdrawableSat From 274afe7419163d467739b7188b527469b7fd9604 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 6 Apr 2026 05:10:06 +0200 Subject: [PATCH 12/14] fix: remove unused import and fix icon button width Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index 95720d597..980581b0d 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height From c9893a8408c9d18d59dc4b40532895f2aaa25791 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 6 Apr 2026 14:59:08 +0200 Subject: [PATCH 13/14] refactor: fix modifier place in preview fn --- app/src/main/java/to/bitkit/ui/components/Button.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index d15c177c7..dc91fb4f8 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -453,9 +453,9 @@ private fun SecondaryButtonPreview() { SecondaryButton(text = "Secondary", hazeState = hazeState, onClick = {}) SecondaryButton( text = "Secondary With padding", - modifier = Modifier.padding(horizontal = 32.dp), hazeState = hazeState, onClick = {}, + modifier = Modifier.padding(horizontal = 32.dp) ) SecondaryButton( text = "Secondary With Icon", From 38cadda338258b69f74dbae28ca28e4a8119f21c Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 6 Apr 2026 19:02:53 +0200 Subject: [PATCH 14/14] chore: remove unused composition locals --- app/src/main/java/to/bitkit/ui/Locals.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/Locals.kt b/app/src/main/java/to/bitkit/ui/Locals.kt index b766d86af..5e669eb46 100644 --- a/app/src/main/java/to/bitkit/ui/Locals.kt +++ b/app/src/main/java/to/bitkit/ui/Locals.kt @@ -4,7 +4,6 @@ import androidx.compose.material3.DrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf -import dev.chrisbanes.haze.HazeState import to.bitkit.models.BalanceState import to.bitkit.repositories.CurrencyState import to.bitkit.viewmodels.ActivityListViewModel @@ -30,7 +29,6 @@ val LocalActivityListViewModel = staticCompositionLocalOf { null } val LocalSettingsViewModel = staticCompositionLocalOf { null } val LocalBackupsViewModel = staticCompositionLocalOf { null } -val LocalHazeState = staticCompositionLocalOf { null } val appViewModel: AppViewModel? @Composable get() = LocalAppViewModel.current @@ -55,9 +53,3 @@ val settingsViewModel: SettingsViewModel? val backupsViewModel: BackupsViewModel? @Composable get() = LocalBackupsViewModel.current - -val drawerState: DrawerState? - @Composable get() = LocalDrawerState.current - -val hazeState: HazeState? - @Composable get() = LocalHazeState.current