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
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.compose.jetchat.components

import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
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.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.MeshGradientPainter
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin

/**
* A gently animated mesh gradient used as the glow behind the chat [UserInput] bar.
*
* The softness comes entirely from the mesh:
* - [MeshGradientPainter.hasBicubicColor] gives smooth (non-linear) colour blending, and
* - the outer ring of vertices is fully transparent (alpha 0x00), so the glow feathers to
* nothing at its edges.
*
* That means no `.blur()` and no linear gradient are needed to reproduce the effect.
*/
@Composable
fun rememberUserInputGlowMeshGradientPainter(): MeshGradientPainter {
val transition = rememberInfiniteTransition(label = "userInputGlowMesh")
val phase1 by transition.animateFloat(
initialValue = 0f,
targetValue = (2 * PI).toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 2800, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
),
label = "glowPhase1",
)
val phase2 by transition.animateFloat(
initialValue = 0f,
targetValue = (2 * PI).toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 4200, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
),
label = "glowPhase2",
)

return remember {
fun orbit(colIdx: Int, rowIdx: Int, ampX: Float, ampY: Float): Offset {
val angle1 = phase1 + colIdx * 0.85f + rowIdx * 0.65f
val angle2 = phase2 - colIdx * 0.55f + rowIdx * 0.9f
val dx = ampX * (0.72f * cos(angle1) + 0.28f * sin(angle2))
val dy = ampY * (0.72f * sin(angle1) + 0.28f * cos(angle2))
return Offset(dx, dy)
}

val uCols = floatArrayOf(0f, 0.12f, 0.27f, 0.56f, 0.78f, 1f)
val vRows = floatArrayOf(0f, 0.2f, 0.33f, 0.43f, 0.64f, 0.85f, 1f)

val gridColors = arrayOf(
// Row 0: transparent royal-blue boundary (top of the glow).
arrayOf(
Color(0x000B57D0),
Color(0x000B57D0),
Color(0x000B57D0),
Color(0x000B57D0),
Color(0x000B57D0),
Color(0x000B57D0),
),
// Row 1: high royal-blue aura.
arrayOf(
Color(0x00125CCF),
Color(0x3C1F62D8),
Color(0x5C2365D7),
Color(0x66125CCF),
Color(0x451D63D1),
Color(0x001D63D1),
),
// Row 2: mid-upper sky-cerulean aura.
arrayOf(
Color(0x006B88EE),
Color(0x80829CF2),
Color(0xA86E8DF0),
Color(0xB46488EE),
Color(0x825C86EC),
Color(0x005C86EC),
),
// Row 3: lavender left, periwinkle centre, cobalt right shoulder.
arrayOf(
Color(0x00B6ABFB),
Color(0xEEB6ABFB),
Color(0xF4A39BFC),
Color(0xF59D98FC),
Color(0xF25C7CF5),
Color(0x18728CF5),
),
// Row 4: vibrant orchid-lavender left, electric cobalt right (card height).
arrayOf(
Color(0x00C2A7FF),
Color(0xFFC2A7FF),
Color(0xFFC1A6FF),
Color(0xFF687DF9),
Color(0xF04E6EFA),
Color(0x205875FA),
),
// Row 5: crisp indigo-cobalt shadow along the bottom.
arrayOf(
Color(0x007E8AF4),
Color(0xE27E8AF4),
Color(0xF45A6DFB),
Color(0xF6556AFC),
Color(0xFA3C5CFC),
Color(0x204D6AFB),
),
// Row 6: transparent cobalt boundary (bottom of the glow).
arrayOf(
Color(0x005A6DFB),
Color(0x005A6DFB),
Color(0x005A6DFB),
Color(0x00556AFC),
Color(0x003C5CFC),
Color(0x003C5CFC),
),
)

MeshGradientPainter(
rows = 6,
columns = 5,
hasBicubicColor = true,
) {
for (r in 0..6) {
for (c in 0..5) {
val baseU = uCols[c]
val baseV = vRows[r]
val offset = if (r in 1..5 && c in 1..4) {
val ampX = when (r) {
1, 2 -> 0.048f
3 -> 0.042f
4 -> 0.028f
else -> 0.036f
}
val ampY = when (r) {
1, 2 -> 0.028f
3 -> 0.02f
4 -> 0.022f
else -> 0.016f
}
val d = orbit(c, r, ampX, ampY)
Offset(
x = (baseU + d.x).coerceIn(0.04f, 0.96f),
y = (baseV + d.y).coerceIn(0.05f, 0.95f),
)
} else {
Offset(baseU, baseV)
}
setVertex(r, c, offset, gridColors[r][c])
}
}
}
}
}

/**
* Heart reaction mesh gradient for liked chat speech bubbles.
* Vibrant coral, blush, hot pink, and lavender tones.
*/
@Composable
fun rememberHeartReactionMeshGradientPainter(): MeshGradientPainter {
return remember {
MeshGradientPainter(
rows = 2,
columns = 3,
hasBicubicColor = true,
) {
// Row 0 (top edge, y = 0f)
setVertex(0, 0, Offset(0f, 0f), Color(0xFFFF6B6B))
setVertex(0, 1, Offset(0.33f, 0f), Color(0xFFFFD6D6))
setVertex(0, 2, Offset(0.67f, 0f), Color(0xFFE8C3FF))
setVertex(0, 3, Offset(1f, 0f), Color(0xFFE396FF))

// Row 1 (mid, y ~ 0.33f - 0.5f)
setVertex(1, 0, Offset(0f, 0.33f), Color(0xFFFFAAEA))
setVertex(1, 1, Offset(0.33f, 0.33f), Color(0xFFFF6060))
setVertex(1, 2, Offset(0.67f, 0.33f), Color(0xFFFFBCBC))
setVertex(1, 3, Offset(1f, 0.5f), Color(0xFFFF2088))

// Row 2 (bottom edge, y = 1f)
setVertex(2, 0, Offset(0f, 1f), Color(0xFFFFFFFF))
setVertex(2, 1, Offset(0.33f, 1f), Color(0xFFD0BEF9))
setVertex(2, 2, Offset(0.66f, 1f), Color(0xFFFFA298))
setVertex(2, 3, Offset(1f, 1f), Color(0xFFFF9BEB))
}
}
}

/**
* A small static mesh gradient that fills the record (mic) button while recording is active:
* a pink → purple → blue diagonal built from a 2x2-cell mesh (3x3 vertices) with bicubic
* color blending.
*/
@Composable
fun rememberRecordButtonMeshGradientPainter(): MeshGradientPainter {
return remember {
val pink = Color(0xFFF96BD6)
val pinkPurple = Color(0xFFC072EA)
val purple = Color(0xFF9378FF)
val purpleBlue = Color(0xFF585CFF)
val blue = Color(0xFF1E40FF)
val positions = floatArrayOf(0f, 0.5f, 1f)
val colors = arrayOf(
arrayOf(pink, pinkPurple, purple),
arrayOf(pinkPurple, purple, purpleBlue),
arrayOf(purple, purpleBlue, blue),
)
MeshGradientPainter(
rows = 2,
columns = 2,
hasBicubicColor = true,
) {
for (r in 0..2) {
for (c in 0..2) {
setVertex(r, c, Offset(positions[c], positions[r]), colors[r][c])
}
}
}
}
}
Loading
Loading