1package com.peachpasswords.ui.components23import androidx.compose.animation.core.Animatable4import androidx.compose.animation.core.FastOutSlowInEasing5import androidx.compose.animation.core.tween6import androidx.compose.foundation.Canvas7import androidx.compose.foundation.background8import androidx.compose.foundation.layout.Box9import androidx.compose.foundation.layout.fillMaxSize10import androidx.compose.foundation.layout.padding11import androidx.compose.foundation.layout.size12import androidx.compose.foundation.layout.statusBarsPadding13import androidx.compose.foundation.shape.CircleShape14import androidx.compose.runtime.Composable15import androidx.compose.runtime.LaunchedEffect16import androidx.compose.runtime.collectAsState17import androidx.compose.runtime.getValue18import androidx.compose.runtime.remember19import androidx.compose.ui.Alignment20import androidx.compose.ui.Modifier21import androidx.compose.ui.draw.clip22import androidx.compose.ui.geometry.CornerRadius23import androidx.compose.ui.graphics.drawscope.Stroke24import androidx.compose.ui.graphics.graphicsLayer25import androidx.compose.ui.unit.dp26import com.peachpasswords.shared.CopyFeedbackBus27import com.peachpasswords.ui.theme.LocalPeachAccent28import com.peachpasswords.ui.theme.LocalReduceMotion29import kotlinx.coroutines.launch3031@Composable32fun CopyFeedbackOverlay(modifier: Modifier = Modifier) {33 val pulse by CopyFeedbackBus.pulses.collectAsState()34 val accent = LocalPeachAccent.current35 val reduceMotion = LocalReduceMotion.current36 val ringAlpha = remember { Animatable(0f) }37 val pipPulse = remember { Animatable(0f) }3839 LaunchedEffect(pulse, reduceMotion) {40 if (pulse == 0) return@LaunchedEffect41 if (reduceMotion) {42 ringAlpha.snapTo(0.42f)43 pipPulse.snapTo(1f)44 ringAlpha.animateTo(0f, tween(220))45 pipPulse.animateTo(0f, tween(220))46 } else {47 launch {48 ringAlpha.snapTo(0f)49 ringAlpha.animateTo(0.62f, tween(190, easing = FastOutSlowInEasing))50 ringAlpha.animateTo(0f, tween(530, easing = FastOutSlowInEasing))51 }52 launch {53 pipPulse.snapTo(0f)54 pipPulse.animateTo(1f, tween(160, easing = FastOutSlowInEasing))55 pipPulse.animateTo(0f, tween(240, easing = FastOutSlowInEasing))56 }57 }58 }5960 Box(modifier.fillMaxSize()) {61 Canvas(Modifier.fillMaxSize()) {62 val stroke = 1.5.dp.toPx()63 drawRoundRect(64 color = accent.copy(alpha = ringAlpha.value * 0.55f),65 style = Stroke(width = stroke),66 cornerRadius = CornerRadius(20.dp.toPx(), 20.dp.toPx()),67 )68 }69 Box(70 Modifier71 .align(Alignment.TopStart)72 .statusBarsPadding()73 .padding(start = 74.dp, top = 23.dp)74 .size(6.dp)75 .graphicsLayer {76 val scale = 1f + (pipPulse.value * 1.2f)77 scaleX = scale78 scaleY = scale79 }80 .clip(CircleShape)81 .background(accent.copy(alpha = pipPulse.value)),82 )83 }84}