Code
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.Icon
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.dp
import theme.Blue200
import theme.Blue400
import theme.Green200
import theme.Green400
import theme.Red200
import theme.Red400
import theme.Transparent
import theme.Yellow200
import theme.Yellow400
import theme.Zinc100
import theme.Zinc200
import theme.Zinc700
@Composable
fun GlowSearchBox() {
var text by remember { mutableStateOf("") }
BasicTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.width(300.dp),
cursorBrush = Brush.verticalGradient(
colors = listOf(Zinc200, Zinc200)
),
textStyle = LocalTextStyle.current.copy(
color = Zinc200,
),
decorationBox = { field ->
val rotation by rememberInfiniteTransition().animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 3000,
easing = LinearEasing,
)
)
)
val shadowBrush by remember {
derivedStateOf {
sweepGradient(
colors = listOf(
Zinc100.copy(alpha = .2f),
Zinc100.copy(alpha = .2f),
Red400,
Yellow400,
Green400,
Blue400,
Zinc100.copy(alpha = .2f),
Zinc100.copy(alpha = .2f),
),
rotation = rotation,
)
}
}
val borderBrush by remember {
derivedStateOf {
sweepGradient(
colors = listOf(
Transparent,
Transparent,
Red200,
Yellow200,
Green200,
Blue200,
Transparent,
Transparent,
),
rotation = rotation,
)
}
}
Row(
modifier = Modifier
.dropShadow(
shape = CircleShape,
) {
alpha = .5f
radius = 40f
brush = shadowBrush
}
.dropShadow(
shape = CircleShape,
) {
alpha = .2f
spread = 50f
radius = 400f
brush = shadowBrush
}
.background(
color = Zinc700,
shape = CircleShape
)
.border(
width = 1.dp,
brush = borderBrush,
shape = CircleShape,
)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Rounded.Search,
contentDescription = null,
tint = Zinc200,
)
Spacer(Modifier.width(12.dp))
field()
}
}
)
}
@Stable
fun sweepGradient(
colors: List<Color>,
center: Offset = Offset.Unspecified,
rotation: Float = 0f,
): Brush {
require(colors.size >= 2) { "At least 2 colors are required" }
val rotationFraction = (-rotation / 360f) % 1f
val normalizedRotation = if (rotationFraction < 0) rotationFraction + 1f else rotationFraction
val step = 1f / colors.size
val rotatedStops = mutableListOf<Pair<Float, Color>>()
for (i in colors.indices) {
val originalStop = i * step
val rotatedStop = (originalStop + normalizedRotation) % 1f
rotatedStops.add(rotatedStop to colors[i])
}
rotatedStops.sortBy { it.first }
val firstStop = rotatedStops.first().first
val lastStop = rotatedStops.last().first
if (firstStop > 0.0001f) {
val colorBeforeWrap = rotatedStops.last().second
val colorAfterWrap = rotatedStops.first().second
val gapSize = (1f - lastStop) + firstStop
val fractionAt0 = (1f - lastStop) / gapSize
val interpolatedColor = lerp(colorBeforeWrap, colorAfterWrap, fractionAt0)
rotatedStops.add(0, 0f to interpolatedColor)
rotatedStops.add(1f to interpolatedColor)
} else {
val lastColor = rotatedStops.last().second
rotatedStops.add(1f to lastColor)
}
return Brush.sweepGradient(
colorStops = rotatedStops.toTypedArray(),
center = center,
)
}