Skip to content

Commit d233497

Browse files
committed
feat: glow effect
1 parent c3763db commit d233497

File tree

1 file changed

+195
-40
lines changed

1 file changed

+195
-40
lines changed

app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt

Lines changed: 195 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package to.bitkit.ui.components
22

33
import androidx.annotation.DrawableRes
4+
import androidx.compose.animation.core.LinearEasing
5+
import androidx.compose.animation.core.RepeatMode
6+
import androidx.compose.animation.core.animateFloat
7+
import androidx.compose.animation.core.infiniteRepeatable
8+
import androidx.compose.animation.core.rememberInfiniteTransition
9+
import androidx.compose.animation.core.tween
410
import androidx.compose.foundation.Image
11+
import androidx.compose.foundation.background
12+
import androidx.compose.foundation.border
513
import androidx.compose.foundation.layout.Arrangement
614
import androidx.compose.foundation.layout.Box
715
import androidx.compose.foundation.layout.Column
@@ -13,14 +21,21 @@ import androidx.compose.foundation.layout.size
1321
import androidx.compose.foundation.lazy.grid.GridCells
1422
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
1523
import androidx.compose.foundation.lazy.grid.items
24+
import androidx.compose.foundation.shape.RoundedCornerShape
1625
import androidx.compose.material3.Icon
1726
import androidx.compose.material3.IconButton
18-
import androidx.compose.material3.ShapeDefaults
1927
import androidx.compose.runtime.Composable
2028
import androidx.compose.runtime.LaunchedEffect
29+
import androidx.compose.runtime.getValue
2130
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.draw.alpha
2232
import androidx.compose.ui.draw.clip
33+
import androidx.compose.ui.draw.drawBehind
34+
import androidx.compose.ui.graphics.Brush
2335
import androidx.compose.ui.graphics.Color
36+
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
37+
import androidx.compose.ui.graphics.nativeCanvas
38+
import androidx.compose.ui.graphics.toArgb
2439
import androidx.compose.ui.layout.ContentScale
2540
import androidx.compose.ui.platform.testTag
2641
import androidx.compose.ui.res.painterResource
@@ -48,6 +63,7 @@ fun SuggestionCard(
4863
duration: Duration? = null,
4964
size: Int = 152,
5065
captionColor: Color = Colors.White64,
66+
dismissable: Boolean = true,
5167
onClick: () -> Unit,
5268
) {
5369
LaunchedEffect(Unit) {
@@ -57,57 +73,195 @@ fun SuggestionCard(
5773
}
5874
}
5975

60-
Box(
61-
modifier = modifier
62-
.size(size.dp)
63-
.clip(ShapeDefaults.Large)
64-
.gradientBackground(gradientColor)
65-
.clickableAlpha { onClick() }
66-
) {
67-
Column(
76+
Box(modifier = modifier) {
77+
if (!dismissable) {
78+
GlowEffect(
79+
size = size,
80+
color = gradientColor,
81+
modifier = Modifier.size(size.dp)
82+
)
83+
}
84+
85+
Box(
6886
modifier = Modifier
69-
.fillMaxWidth()
70-
.padding(horizontal = 16.dp, vertical = 12.dp),
71-
verticalArrangement = Arrangement.spacedBy(8.dp)
87+
.size(size.dp)
88+
.clip(RoundedCornerShape(16.dp))
89+
.then(
90+
if (dismissable) {
91+
Modifier.gradientBackground(gradientColor)
92+
} else {
93+
Modifier
94+
.border(
95+
width = 1.dp,
96+
color = getBorderColorForGradient(gradientColor),
97+
shape = RoundedCornerShape(16.dp)
98+
)
99+
.gradientBackground(gradientColor)
100+
}
101+
)
102+
.clickableAlpha { onClick() }
72103
) {
73-
Row(
104+
// Shade effect for dismissable cards (similar to the Shade component in RN)
105+
if (dismissable) {
106+
Box(
107+
modifier = Modifier
108+
.fillMaxSize()
109+
.background(
110+
brush = Brush.verticalGradient(
111+
colors = listOf(
112+
Color.Transparent,
113+
Color.Black.copy(alpha = 0.6f)
114+
),
115+
startY = size * 0.4f,
116+
endY = size.toFloat()
117+
)
118+
)
119+
)
120+
}
121+
122+
Column(
74123
modifier = Modifier
75124
.fillMaxWidth()
76-
.weight(1f)
125+
.padding(horizontal = 16.dp, vertical = 12.dp),
126+
verticalArrangement = Arrangement.spacedBy(8.dp)
77127
) {
78-
Image(
79-
painter = painterResource(icon),
80-
contentDescription = null,
81-
contentScale = ContentScale.FillHeight,
82-
modifier = Modifier.weight(1f)
128+
Row(
129+
modifier = Modifier
130+
.fillMaxWidth()
131+
.weight(1f)
132+
) {
133+
Image(
134+
painter = painterResource(icon),
135+
contentDescription = null,
136+
contentScale = ContentScale.FillHeight,
137+
modifier = Modifier.weight(1f)
138+
)
139+
140+
if (duration == null && onClose != null && dismissable) {
141+
IconButton(
142+
onClick = onClose,
143+
modifier = Modifier
144+
.size(16.dp)
145+
.testTag("SuggestionDismiss")
146+
) {
147+
Icon(
148+
painter = painterResource(R.drawable.ic_x),
149+
contentDescription = null,
150+
tint = Colors.White,
151+
)
152+
}
153+
}
154+
}
155+
156+
Headline20(
157+
text = AnnotatedString(title),
158+
color = Colors.White,
159+
)
160+
161+
CaptionB(
162+
text = description,
163+
color = captionColor,
83164
)
165+
}
166+
}
167+
}
168+
}
84169

85-
if (duration == null && onClose != null) {
86-
IconButton(
87-
onClick = onClose,
88-
modifier = Modifier
89-
.size(16.dp)
90-
.testTag("SuggestionDismiss")
91-
) {
92-
Icon(
93-
painter = painterResource(R.drawable.ic_x),
94-
contentDescription = null,
95-
tint = Colors.White,
170+
@Composable
171+
private fun GlowEffect(
172+
size: Int,
173+
color: Color,
174+
modifier: Modifier = Modifier,
175+
) {
176+
val infiniteTransition = rememberInfiniteTransition(label = "glowTransition")
177+
val glowAlpha by infiniteTransition.animateFloat(
178+
initialValue = 0f,
179+
targetValue = 1f,
180+
animationSpec = infiniteRepeatable(
181+
animation = tween(1100, easing = LinearEasing),
182+
repeatMode = RepeatMode.Reverse
183+
),
184+
label = "glowAlpha"
185+
)
186+
187+
val (shadowColor, _, radialGradientColor) = getGlowColors(color)
188+
189+
Box(modifier = modifier) {
190+
// Outer glow with animated opacity
191+
Box(
192+
modifier = Modifier
193+
.fillMaxSize()
194+
.alpha(glowAlpha)
195+
.drawBehind {
196+
drawIntoCanvas { canvas ->
197+
val paint = android.graphics.Paint().apply {
198+
this.color = shadowColor.toArgb()
199+
setShadowLayer(15f, 0f, 0f, shadowColor.toArgb())
200+
isAntiAlias = true
201+
}
202+
203+
val rect = android.graphics.RectF(
204+
5f,
205+
5f,
206+
size.toFloat() - 5f,
207+
size.toFloat() - 5f
208+
)
209+
210+
canvas.nativeCanvas.drawRoundRect(
211+
rect,
212+
16f,
213+
16f,
214+
paint
96215
)
97216
}
98217
}
99-
}
218+
)
100219

101-
Headline20(
102-
text = AnnotatedString(title),
103-
color = Colors.White,
104-
)
220+
// Static radial gradient overlay
221+
Box(
222+
modifier = Modifier
223+
.fillMaxSize()
224+
.alpha(0.4f)
225+
.background(
226+
brush = Brush.radialGradient(
227+
colors = listOf(radialGradientColor, color),
228+
center = androidx.compose.ui.geometry.Offset(
229+
size / 2f,
230+
size / 2f
231+
),
232+
radius = size / 2f
233+
),
234+
shape = RoundedCornerShape(16.dp)
235+
)
236+
)
237+
}
238+
}
105239

106-
CaptionB(
107-
text = description,
108-
color = captionColor,
109-
)
110-
}
240+
private fun getGlowColors(color: Color): Triple<Color, Color, Color> {
241+
return when (color) {
242+
Colors.Brand24 -> Triple(
243+
Color(200, 48, 0), // shadowColor
244+
Color(255, 68, 0), // borderColor
245+
Color(100, 24, 0) // radialGradientColor
246+
)
247+
248+
else -> Triple(
249+
Color(130, 65, 175), // shadowColor (default purple)
250+
Color(185, 92, 232), // borderColor
251+
Color(65, 32, 80) // radialGradientColor
252+
)
253+
}
254+
}
255+
256+
private fun getBorderColorForGradient(color: Color): Color {
257+
return when (color) {
258+
Colors.Brand24 -> Color(255, 68, 0)
259+
Colors.Purple24 -> Color(185, 92, 232)
260+
Colors.Blue24 -> Color(92, 185, 232)
261+
Colors.Green24 -> Color(92, 232, 185)
262+
Colors.Yellow24 -> Color(232, 185, 92)
263+
Colors.Red24 -> Color(232, 92, 92)
264+
else -> Color(185, 92, 232)
111265
}
112266
}
113267

@@ -127,6 +281,7 @@ private fun Preview() {
127281
icon = item.icon,
128282
onClose = {},
129283
onClick = {},
284+
dismissable = item != Suggestion.LIGHTNING_READY,
130285
duration = 5.seconds.takeIf { item == Suggestion.LIGHTNING_READY }
131286
)
132287
}

0 commit comments

Comments
 (0)