ProCelebration.kt

6.5 KB · raw

1package com.peachpasswords.ui.settings23import androidx.compose.animation.core.Animatable4import androidx.compose.animation.core.CubicBezierEasing5import androidx.compose.animation.core.keyframes6import androidx.compose.animation.core.tween7import androidx.compose.foundation.Canvas8import androidx.compose.foundation.background9import androidx.compose.foundation.clickable10import androidx.compose.foundation.layout.Box11import androidx.compose.foundation.layout.Column12import androidx.compose.foundation.layout.fillMaxSize13import androidx.compose.foundation.layout.offset14import androidx.compose.foundation.layout.padding15import androidx.compose.foundation.layout.size16import androidx.compose.material3.Text17import androidx.compose.runtime.Composable18import androidx.compose.runtime.LaunchedEffect19import androidx.compose.runtime.getValue20import androidx.compose.runtime.remember21import androidx.compose.ui.Alignment22import androidx.compose.ui.Modifier23import androidx.compose.ui.geometry.Offset24import androidx.compose.ui.graphics.Brush25import androidx.compose.ui.graphics.graphicsLayer26import androidx.compose.ui.text.TextStyle27import androidx.compose.ui.text.font.FontStyle28import androidx.compose.ui.text.style.TextAlign29import androidx.compose.ui.unit.dp30import androidx.compose.ui.unit.sp31import com.peachpasswords.ui.components.PeachMark32import com.peachpasswords.ui.theme.LocalPeachAccent33import com.peachpasswords.ui.theme.LocalReduceMotion34import com.peachpasswords.ui.theme.peachColors35import com.peachpasswords.ui.theme.PeachType36import kotlinx.coroutines.delay37import kotlinx.coroutines.launch3839// CSS cubic-bezier(0.16, 1, 0.3, 1) — the browser's bloom/wordmark-in easing40// (a pronounced ease-out-expo feel).41private val ExpoOut = CubicBezierEasing(0.16f, 1f, 0.3f, 1f)4243/**44 * Full-screen Pro activation celebration. Ports the browser's `.peach-pro-celebration`45 * (ProCelebration.tsx + popup.css): two corner accent blooms behind a near-black46 * shell, PeachMark with a glow pulse (`peach-pro-bloom`), then "peach pro" /47 * "thank you." fading up in a stagger (`peach-pro-wordmark-in`). Holds ~1800ms,48 * fades out over 400ms (`is-leaving`), then calls [onDone]. A tap skips straight49 * to [onDone] without playing the exit fade.50 */51@Composable52fun ProCelebration(onDone: () -> Unit) {53    val accent = LocalPeachAccent.current54    val reduceMotion = LocalReduceMotion.current5556    val glow = remember { Animatable(0f) }57    val wordmarkAlpha = remember { Animatable(0f) }58    val wordmarkOffset = remember { Animatable(4f) }59    val thankyouAlpha = remember { Animatable(0f) }60    val thankyouOffset = remember { Animatable(4f) }61    val exitAlpha = remember { Animatable(1f) }6263    LaunchedEffect(Unit) {64        if (reduceMotion) {65            wordmarkAlpha.snapTo(1f); wordmarkOffset.snapTo(0f)66            thankyouAlpha.snapTo(1f); thankyouOffset.snapTo(0f)67        } else {68            launch {69                // peach-pro-bloom: 0 -> peak at 40% -> 0, over 600ms.70                glow.animateTo(1f, keyframes { durationMillis = 600; 0f at 0; 1f at 240; 0f at 600 })71            }72            launch {73                delay(500)74                launch { wordmarkAlpha.animateTo(1f, tween(400, easing = ExpoOut)) }75                wordmarkOffset.animateTo(0f, tween(400, easing = ExpoOut))76            }77            launch {78                delay(800)79                launch { thankyouAlpha.animateTo(1f, tween(400, easing = ExpoOut)) }80                thankyouOffset.animateTo(0f, tween(400, easing = ExpoOut))81            }82        }83        delay(1800)84        if (!reduceMotion) exitAlpha.animateTo(0f, tween(400))85        onDone()86    }8788    Box(89        modifier = Modifier90            .fillMaxSize()91            .graphicsLayer { alpha = exitAlpha.value }92            .background(peachColors.Bg)93            .clickable(onClick = onDone),94        contentAlignment = Alignment.Center,95    ) {96        // Two corner blooms (radial-gradient(...at 0% 0%) / (...at 100% 100%)97        // in popup.css), replacing the old single centred flat-alpha circle.98        Canvas(Modifier.fillMaxSize()) {99            drawRect(100                brush = Brush.radialGradient(101                    colors = listOf(accent.copy(alpha = 0.16f), accent.copy(alpha = 0f)),102                    center = Offset.Zero,103                    radius = size.maxDimension * 0.9f,104                ),105            )106            drawRect(107                brush = Brush.radialGradient(108                    colors = listOf(accent.copy(alpha = 0.12f), accent.copy(alpha = 0f)),109                    center = Offset(size.width, size.height),110                    radius = size.maxDimension * 0.9f,111                ),112            )113        }114        Column(horizontalAlignment = Alignment.CenterHorizontally) {115            Box(Modifier.size(180.dp), contentAlignment = Alignment.Center) {116                // Soft glow behind the mark, standing in for the CSS drop-shadow117                // pulse — a portable Compose equivalent that works below API 31118                // (Modifier.blur's minimum), unlike a true blur filter.119                Canvas(Modifier.fillMaxSize()) {120                    if (glow.value > 0f) {121                        drawCircle(122                            brush = Brush.radialGradient(123                                colors = listOf(accent.copy(alpha = 0.45f * glow.value), accent.copy(alpha = 0f)),124                                radius = 90.dp.toPx(),125                            ),126                            radius = 90.dp.toPx(),127                        )128                    }129                }130                PeachMark(size = 80.dp)131            }132            Text(133                "peach pro",134                color = accent,135                style = TextStyle(fontFamily = PeachType.Display, fontStyle = FontStyle.Italic, fontSize = 44.sp),136                textAlign = TextAlign.Center,137                modifier = Modifier138                    .padding(top = 18.dp)139                    .offset(y = wordmarkOffset.value.dp)140                    .graphicsLayer { alpha = wordmarkAlpha.value },141            )142            Text(143                "thank you.",144                color = peachColors.T1,145                style = TextStyle(fontFamily = PeachType.Display, fontStyle = FontStyle.Italic, fontSize = 26.sp),146                textAlign = TextAlign.Center,147                modifier = Modifier148                    .padding(top = 6.dp)149                    .offset(y = thankyouOffset.value.dp)150                    .graphicsLayer { alpha = thankyouAlpha.value },151            )152        }153    }154}