package com.peachpasswords.ui.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import com.peachpasswords.shared.CopyFeedbackBus import com.peachpasswords.ui.theme.LocalPeachAccent import com.peachpasswords.ui.theme.LocalReduceMotion import kotlinx.coroutines.launch @Composable fun CopyFeedbackOverlay(modifier: Modifier = Modifier) { val pulse by CopyFeedbackBus.pulses.collectAsState() val accent = LocalPeachAccent.current val reduceMotion = LocalReduceMotion.current val ringAlpha = remember { Animatable(0f) } val pipPulse = remember { Animatable(0f) } LaunchedEffect(pulse, reduceMotion) { if (pulse == 0) return@LaunchedEffect if (reduceMotion) { ringAlpha.snapTo(0.42f) pipPulse.snapTo(1f) ringAlpha.animateTo(0f, tween(220)) pipPulse.animateTo(0f, tween(220)) } else { launch { ringAlpha.snapTo(0f) ringAlpha.animateTo(0.62f, tween(190, easing = FastOutSlowInEasing)) ringAlpha.animateTo(0f, tween(530, easing = FastOutSlowInEasing)) } launch { pipPulse.snapTo(0f) pipPulse.animateTo(1f, tween(160, easing = FastOutSlowInEasing)) pipPulse.animateTo(0f, tween(240, easing = FastOutSlowInEasing)) } } } Box(modifier.fillMaxSize()) { Canvas(Modifier.fillMaxSize()) { val stroke = 1.5.dp.toPx() drawRoundRect( color = accent.copy(alpha = ringAlpha.value * 0.55f), style = Stroke(width = stroke), cornerRadius = CornerRadius(20.dp.toPx(), 20.dp.toPx()), ) } Box( Modifier .align(Alignment.TopStart) .statusBarsPadding() .padding(start = 74.dp, top = 23.dp) .size(6.dp) .graphicsLayer { val scale = 1f + (pipPulse.value * 1.2f) scaleX = scale scaleY = scale } .clip(CircleShape) .background(accent.copy(alpha = pipPulse.value)), ) } }