package com.peachpasswords.ui.settings import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.peachpasswords.ui.components.PeachField import com.peachpasswords.ui.components.RuneGridBackground import com.peachpasswords.ui.components.PeachTopBar import com.peachpasswords.ui.theme.LocalPeachAccent import com.peachpasswords.ui.theme.LocalPeachColors import com.peachpasswords.ui.theme.PeachSpacing import com.peachpasswords.ui.theme.peachColors import com.peachpasswords.ui.theme.peachDark import com.peachpasswords.ui.theme.PeachType import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import com.peachpasswords.ui.onboarding.ONBOARD_KEY import com.peachpasswords.ui.onboarding.ONBOARD_PREFS import com.peachpasswords.sync.SyncFreshStartResetter import com.peachpasswords.vault.VaultRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class PanicViewModel @Inject constructor( private val vault: VaultRepository, private val syncFreshStart: SyncFreshStartResetter, @ApplicationContext private val appContext: Context, ) : ViewModel() { val entryCount: Int get() = vault.getEntries().size fun destroy(onDone: () -> Unit) = viewModelScope.launch { vault.destroyVault() syncFreshStart.reset() // Destroying the vault puts the app back to factory state, so clear the // "already onboarded" flag too — otherwise the next launch skipped the // intro and dropped straight onto create-vault, which is not what a // fresh start should look like. appContext.getSharedPreferences(ONBOARD_PREFS, Context.MODE_PRIVATE) .edit().remove(ONBOARD_KEY).apply() onDone() } } @Composable fun PanicScreen( onBack: () -> Unit, onDestroyed: () -> Unit, vm: PanicViewModel = hiltViewModel(), ) { var step by remember { mutableStateOf(1) } val entryCount = remember { vm.entryCount } // Panic always renders dark regardless of app theme: its tokens must stay // readable on the intentional near-black field below (keep the user's accent). val accent = LocalPeachAccent.current val darkPalette = remember(accent) { peachDark(accent) } CompositionLocalProvider(LocalPeachColors provides darkPalette) { // The panic flow is intentionally cold: no bloom, a flat near-black field so // it reads as a dead-end rather than another lit-up screen in the app. Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0C0A06))) { // Browser parity: the final step runs a drifting rune field with runes // flaring and dying behind it. The earlier steps stay deliberately cold // and flat — the chaos is what marks this as the point of no return. if (step == 3) { RuneGridBackground( modifier = Modifier.fillMaxSize(), alpha = 0.22f, glowing = true, ) } Column( modifier = Modifier .fillMaxSize() .systemBarsPadding() .padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.md), ) { PeachTopBar( title = "Panic", leading = { Icon( Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "back", tint = peachColors.T0, modifier = Modifier .size(22.dp) .clip(RoundedCornerShape(PeachSpacing.sm)) .clickable(onClick = onBack), ) }, ) Box( modifier = Modifier.fillMaxSize().padding(horizontal = PeachSpacing.sm), contentAlignment = Alignment.Center, ) { Column(verticalArrangement = Arrangement.Center) { when (step) { 1 -> Step1(onCancel = onBack, onContinue = { step = 2 }) 2 -> Step2(entryCount = entryCount, onBack = { step = 1 }, onContinue = { step = 3 }) else -> Step3(onDestroy = { vm.destroy(onDestroyed) }) } } } } } } } @Composable private fun Step1(onCancel: () -> Unit, onContinue: () -> Unit) { Headline("destroy vault", 44.sp, peachColors.Breach) Spacer(Modifier.height(PeachSpacing.lg)) Text( "This will permanently delete your vault and all saved passwords from this device. This cannot be undone.", color = peachColors.T1, style = PeachType.body, ) Spacer(Modifier.height(PeachSpacing.xl)) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm), ) { GhostButton("Cancel", peachColors.T1, onCancel) DangerButton("I understand, continue", enabled = true, onClick = onContinue) } } @Composable private fun Step2(entryCount: Int, onBack: () -> Unit, onContinue: () -> Unit) { Headline("are you sure?", 32.sp, peachColors.Breach) Spacer(Modifier.height(PeachSpacing.lg)) Text( "You will lose access to all $entryCount saved passwords.", color = peachColors.T1, style = PeachType.body, ) Spacer(Modifier.height(PeachSpacing.xl)) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm), ) { GhostButton("Go back", peachColors.T1, onBack) DangerButton("Yes, destroy it", enabled = true, onClick = onContinue) } } @Composable private fun Step3(onDestroy: () -> Unit) { var typed by remember { mutableStateOf("") } val confirmed = typed == "DESTROY" Headline("type DESTROY to confirm", 28.sp, peachColors.T0) Spacer(Modifier.height(PeachSpacing.lg)) PeachField( label = "confirmation", value = typed, onValueChange = { typed = it }, ) Spacer(Modifier.height(PeachSpacing.xl)) DangerButton("Destroy vault", enabled = confirmed, onClick = onDestroy) } @Composable private fun Headline(text: String, fontSize: androidx.compose.ui.unit.TextUnit, color: Color) { Text( text, color = color, style = TextStyle( fontFamily = PeachType.Display, fontStyle = FontStyle.Italic, fontSize = fontSize, ), ) } @Composable private fun GhostButton(label: String, color: Color, onClick: () -> Unit) { Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(999.dp)) .border(1.dp, color.copy(alpha = 0.4f), RoundedCornerShape(999.dp)) .clickable(onClick = onClick) .padding(horizontal = PeachSpacing.lg, vertical = 11.dp), contentAlignment = Alignment.Center, ) { Text(label, color = color, style = PeachType.sortPill.copy(fontSize = 11.sp)) } } @Composable private fun DangerButton(label: String, enabled: Boolean, onClick: () -> Unit) { val alpha = if (enabled) 1f else 0.4f Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(999.dp)) .background(peachColors.BreachBg.copy(alpha = peachColors.BreachBg.alpha * alpha)) .border(1.dp, peachColors.Breach.copy(alpha = 0.55f * alpha), RoundedCornerShape(999.dp)) .clickable(enabled = enabled, onClick = onClick) .padding(horizontal = PeachSpacing.lg, vertical = 11.dp), contentAlignment = Alignment.Center, ) { Text( label, color = peachColors.Breach.copy(alpha = alpha), style = PeachType.sortPill.copy(fontSize = 11.sp), ) } }