package com.peachpasswords.ui.components import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.peachpasswords.ui.theme.LocalPeachAccent import com.peachpasswords.ui.theme.LocalReduceMotion import com.peachpasswords.ui.theme.peachColors /** * Copy button with the two-effect copy feedback (spec §4.4): a diagonal sheen * sweeps once over ~600ms and an accent drain bar fills 0→100% over 1.4s. Both * collapse instantly when the copied latch clears, and are skipped under * reduce-motion. * * Extracted from EntryDetailScreen so the generator drawer (EntryEditScreen, * detail bottom sheet) can reuse the identical drain animation. */ @Composable fun CopyButton(copied: Boolean, onCopy: () -> Unit) { val accent = LocalPeachAccent.current val reduceMotion = LocalReduceMotion.current val animate = copied && !reduceMotion val drain by animateFloatAsState( targetValue = if (copied) 1f else 0f, animationSpec = if (animate) tween(1400, easing = LinearEasing) else tween(0), label = "copy-drain", ) val sheen by animateFloatAsState( targetValue = if (copied) 1f else 0f, animationSpec = if (animate) tween(600, easing = LinearEasing) else tween(0), label = "copy-sheen", ) Box( Modifier .size(36.dp) .clip(RoundedCornerShape(6.dp)) .background(peachColors.S2) .clickable(onClick = onCopy) .drawBehind { if (animate && sheen < 1f) { val sweepX = (sheen * 2f - 0.5f) * size.width val band = size.width * 0.5f drawRect( brush = Brush.linearGradient( colors = listOf(Color.Transparent, accent.copy(alpha = 0.35f), Color.Transparent), start = Offset(sweepX - band, 0f), end = Offset(sweepX + band, size.height), ), ) } if (drain > 0f) { val h = 2.dp.toPx() drawRect( color = accent, topLeft = Offset(0f, size.height - h), size = Size(size.width * drain, h), ) } }, contentAlignment = Alignment.Center, ) { Icon( if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy, contentDescription = "copy", tint = if (copied) accent else peachColors.T1, modifier = Modifier.size(15.dp), ) } }