11package to.bitkit.ui.components
22
33import androidx.annotation.DrawableRes
4- import androidx.compose.animation.core.LinearEasing
54import androidx.compose.animation.core.RepeatMode
65import androidx.compose.animation.core.animateFloat
76import androidx.compose.animation.core.infiniteRepeatable
87import androidx.compose.animation.core.rememberInfiniteTransition
98import androidx.compose.animation.core.tween
109import androidx.compose.foundation.Image
11- import androidx.compose.foundation.background
12- import androidx.compose.foundation.border
1310import androidx.compose.foundation.layout.Arrangement
1411import androidx.compose.foundation.layout.Box
1512import androidx.compose.foundation.layout.Column
@@ -21,21 +18,15 @@ import androidx.compose.foundation.layout.size
2118import androidx.compose.foundation.lazy.grid.GridCells
2219import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
2320import androidx.compose.foundation.lazy.grid.items
24- import androidx.compose.foundation.shape.RoundedCornerShape
2521import androidx.compose.material3.Icon
2622import androidx.compose.material3.IconButton
23+ import androidx.compose.material3.ShapeDefaults
2724import androidx.compose.runtime.Composable
2825import androidx.compose.runtime.LaunchedEffect
2926import androidx.compose.runtime.getValue
3027import androidx.compose.ui.Modifier
31- import androidx.compose.ui.draw.alpha
3228import androidx.compose.ui.draw.clip
33- import androidx.compose.ui.draw.drawBehind
34- import androidx.compose.ui.graphics.Brush
3529import 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
3930import androidx.compose.ui.layout.ContentScale
4031import androidx.compose.ui.platform.testTag
4132import androidx.compose.ui.res.painterResource
@@ -47,7 +38,8 @@ import kotlinx.coroutines.delay
4738import to.bitkit.R
4839import to.bitkit.models.Suggestion
4940import to.bitkit.ui.shared.util.clickableAlpha
50- import to.bitkit.ui.shared.util.gradientBackground
41+ import to.bitkit.ui.shared.util.gradientLinearBackground
42+ import to.bitkit.ui.shared.util.gradientRadialBackground
5143import to.bitkit.ui.theme.Colors
5244import kotlin.time.Duration
5345import kotlin.time.Duration.Companion.seconds
@@ -65,206 +57,87 @@ fun SuggestionCard(
6557 captionColor : Color = Colors .White64 ,
6658 onClick : () -> Unit ,
6759) {
68- val dismissable = onClose != null
69-
7060 LaunchedEffect (Unit ) {
7161 duration?.let {
7262 delay(it)
7363 onClose?.invoke()
7464 }
7565 }
7666
77- Box (modifier = modifier) {
78- if (! dismissable) {
79- GlowEffect (
80- size = size,
81- color = gradientColor,
82- modifier = Modifier .size(size.dp)
83- )
84- }
85-
86- Box (
87- modifier = Modifier
88- .size(size.dp)
89- .clip(RoundedCornerShape (16 .dp))
90- .then(
91- if (dismissable) {
92- Modifier .gradientBackground(gradientColor)
93- } else {
94- Modifier
95- .border(
96- width = 1 .dp,
97- color = getBorderColorForGradient(gradientColor),
98- shape = RoundedCornerShape (16 .dp)
99- )
100- .gradientBackground(gradientColor)
101- }
102- )
103- .clickableAlpha { onClick() }
104- ) {
105- // Shade effect for dismissable cards (similar to the Shade component in RN)
106- if (dismissable) {
107- Box (
108- modifier = Modifier
109- .fillMaxSize()
110- .background(
111- brush = Brush .verticalGradient(
112- colors = listOf (
113- Color .Transparent ,
114- Color .Black .copy(alpha = 0.6f )
115- ),
116- startY = size * 0.4f ,
117- endY = size.toFloat()
118- )
119- )
120- )
121- }
122-
123- Column (
124- modifier = Modifier
125- .fillMaxWidth()
126- .padding(horizontal = 16 .dp, vertical = 12 .dp),
127- verticalArrangement = Arrangement .spacedBy(8 .dp)
128- ) {
129- Row (
130- modifier = Modifier
131- .fillMaxWidth()
132- .weight(1f )
133- ) {
134- Image (
135- painter = painterResource(icon),
136- contentDescription = null ,
137- contentScale = ContentScale .FillHeight ,
138- modifier = Modifier .weight(1f )
139- )
140-
141- if (duration == null && onClose != null ) {
142- IconButton (
143- onClick = onClose,
144- modifier = Modifier
145- .size(16 .dp)
146- .testTag(" SuggestionDismiss" )
147- ) {
148- Icon (
149- painter = painterResource(R .drawable.ic_x),
150- contentDescription = null ,
151- tint = Colors .White ,
152- )
153- }
154- }
155- }
156-
157- Headline20 (
158- text = AnnotatedString (title),
159- color = Colors .White ,
160- )
161-
162- CaptionB (
163- text = description,
164- color = captionColor,
165- )
166- }
167- }
168- }
169- }
67+ val isDismissible = onClose != null
17068
171- @Composable
172- private fun GlowEffect (
173- size : Int ,
174- color : Color ,
175- modifier : Modifier = Modifier ,
176- ) {
177- val infiniteTransition = rememberInfiniteTransition(label = " glowTransition" )
69+ // Glow animation for non-dismissible cards
70+ val infiniteTransition = rememberInfiniteTransition(label = " glow" )
17871 val glowAlpha by infiniteTransition.animateFloat(
179- initialValue = 0f ,
72+ initialValue = 0.24f ,
18073 targetValue = 1f ,
18174 animationSpec = infiniteRepeatable(
182- animation = tween(1100 , easing = LinearEasing ),
75+ animation = tween(1000 ),
18376 repeatMode = RepeatMode .Reverse
18477 ),
185- label = " glowAlpha "
78+ label = " glow_alpha "
18679 )
18780
188- val (shadowColor, _, radialGradientColor) = getGlowColors(color)
189-
190- Box (modifier = modifier) {
191- // Outer glow with animated opacity
192- Box (
81+ Box (
82+ modifier = modifier
83+ .size(size.dp)
84+ .clip(ShapeDefaults .Large )
85+ .then(
86+ if (isDismissible) {
87+ Modifier .gradientLinearBackground(gradientColor)
88+ } else {
89+ Modifier .gradientRadialBackground(gradientColor, glowAlpha)
90+ }
91+ )
92+ .clickableAlpha { onClick() }
93+ ) {
94+ Column (
19395 modifier = Modifier
194- .fillMaxSize()
195- .alpha(glowAlpha)
196- .drawBehind {
197- drawIntoCanvas { canvas ->
198- val paint = android.graphics.Paint ().apply {
199- this .color = shadowColor.toArgb()
200- setShadowLayer(15f , 0f , 0f , shadowColor.toArgb())
201- isAntiAlias = true
202- }
203-
204- val rect = android.graphics.RectF (
205- 5f ,
206- 5f ,
207- size.toFloat() - 5f ,
208- size.toFloat() - 5f
209- )
96+ .fillMaxWidth()
97+ .padding(horizontal = 16 .dp, vertical = 12 .dp),
98+ verticalArrangement = Arrangement .spacedBy(8 .dp)
99+ ) {
100+ Row (
101+ modifier = Modifier
102+ .fillMaxWidth()
103+ .weight(1f )
104+ ) {
105+ Image (
106+ painter = painterResource(icon),
107+ contentDescription = null ,
108+ contentScale = ContentScale .FillHeight ,
109+ modifier = Modifier .weight(1f )
110+ )
210111
211- canvas.nativeCanvas.drawRoundRect(
212- rect,
213- 16f ,
214- 16f ,
215- paint
112+ if (duration == null && onClose != null ) {
113+ IconButton (
114+ onClick = onClose,
115+ modifier = Modifier
116+ .size(16 .dp)
117+ .testTag(" SuggestionDismiss" )
118+ ) {
119+ Icon (
120+ painter = painterResource(R .drawable.ic_x),
121+ contentDescription = null ,
122+ tint = Colors .White ,
216123 )
217124 }
218125 }
219- )
220-
221- // Static radial gradient overlay
222- Box (
223- modifier = Modifier
224- .fillMaxSize()
225- .alpha(0.4f )
226- .background(
227- brush = Brush .radialGradient(
228- colors = listOf (radialGradientColor, color),
229- center = androidx.compose.ui.geometry.Offset (
230- size / 2f ,
231- size / 2f
232- ),
233- radius = size / 2f
234- ),
235- shape = RoundedCornerShape (16 .dp)
236- )
237- )
238- }
239- }
126+ }
240127
241- private fun getGlowColors (color : Color ): Triple <Color , Color , Color > {
242- return when (color) {
243- Colors .Brand24 -> Triple (
244- Color (200 , 48 , 0 ), // shadowColor
245- Color (255 , 68 , 0 ), // borderColor
246- Color (100 , 24 , 0 ) // radialGradientColor
247- )
128+ Headline20 (
129+ text = AnnotatedString (title),
130+ color = Colors .White ,
131+ )
248132
249- else -> Triple (
250- Color ( 130 , 65 , 175 ), // shadowColor (default purple)
251- Color ( 185 , 92 , 232 ), // borderColor
252- Color ( 65 , 32 , 80 ) // radialGradientColor
253- )
133+ CaptionB (
134+ text = description,
135+ color = captionColor,
136+ )
137+ }
254138 }
255139}
256140
257- private fun getBorderColorForGradient (color : Color ): Color {
258- return when (color) {
259- Colors .Brand24 -> Color (255 , 68 , 0 )
260- Colors .Purple24 -> Color (185 , 92 , 232 )
261- Colors .Blue24 -> Color (92 , 185 , 232 )
262- Colors .Green24 -> Color (92 , 232 , 185 )
263- Colors .Yellow24 -> Color (232 , 185 , 92 )
264- Colors .Red24 -> Color (232 , 92 , 92 )
265- else -> Color (185 , 92 , 232 )
266- }
267- }
268141
269142@Preview(showSystemUi = true )
270143@Composable
@@ -281,7 +154,7 @@ private fun Preview() {
281154 description = stringResource(item.description),
282155 icon = item.icon,
283156 onClose = {},
284- onClick = {},
157+ onClick = {}, // All cards are clickable
285158 duration = 5 .seconds.takeIf { item == Suggestion .LIGHTNING_READY }
286159 )
287160 }
0 commit comments