1package com.peachpasswords.ui.settings23import androidx.compose.foundation.background4import androidx.compose.foundation.border5import androidx.compose.foundation.clickable6import androidx.compose.foundation.layout.Arrangement7import androidx.compose.foundation.layout.Box8import androidx.compose.foundation.layout.Column9import androidx.compose.foundation.layout.Row10import androidx.compose.foundation.layout.Spacer11import androidx.compose.foundation.layout.fillMaxSize12import androidx.compose.foundation.layout.fillMaxWidth13import androidx.compose.foundation.layout.padding14import androidx.compose.foundation.layout.size15import androidx.compose.foundation.layout.systemBarsPadding16import androidx.compose.foundation.layout.width17import androidx.compose.foundation.shape.RoundedCornerShape18import androidx.compose.foundation.text.BasicTextField19import androidx.compose.material3.Text20import androidx.compose.runtime.Composable21import androidx.compose.runtime.CompositionLocalProvider22import androidx.compose.runtime.getValue23import androidx.compose.runtime.mutableStateOf24import androidx.compose.runtime.remember25import androidx.compose.runtime.setValue26import androidx.compose.ui.Alignment27import androidx.compose.ui.Modifier28import androidx.compose.ui.draw.clip29import androidx.compose.ui.graphics.Color30import androidx.compose.ui.graphics.SolidColor31import androidx.compose.ui.text.TextStyle32import androidx.compose.ui.text.font.FontStyle33import androidx.compose.ui.text.input.TextFieldValue34import androidx.compose.ui.unit.em35import androidx.compose.ui.unit.sp36import androidx.compose.ui.unit.dp37import androidx.hilt.navigation.compose.hiltViewModel38import androidx.lifecycle.ViewModel39import androidx.lifecycle.viewModelScope40import com.peachpasswords.ui.theme.LocalPeachAccent41import com.peachpasswords.ui.theme.LocalPeachColors42import com.peachpasswords.ui.theme.peachColors43import com.peachpasswords.ui.theme.peachDark44import com.peachpasswords.ui.theme.PeachType45import com.peachpasswords.vault.VaultRepository46import dagger.hilt.android.lifecycle.HiltViewModel47import kotlinx.coroutines.launch48import javax.inject.Inject4950@HiltViewModel51class PanicViewModel @Inject constructor(private val vault: VaultRepository) : ViewModel() {52 val entryCount: Int get() = vault.getEntries().size53 fun destroy(onDone: () -> Unit) = viewModelScope.launch { vault.destroyVault(); onDone() }54}5556@Composable57fun PanicScreen(58 onBack: () -> Unit,59 onDestroyed: () -> Unit,60 vm: PanicViewModel = hiltViewModel(),61) {62 var step by remember { mutableStateOf(1) }63 val entryCount = remember { vm.entryCount }6465 66 67 val accent = LocalPeachAccent.current68 val darkPalette = remember(accent) { peachDark(accent) }6970 CompositionLocalProvider(LocalPeachColors provides darkPalette) {71 72 73 Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0C0A06))) {74 Column(75 modifier = Modifier76 .fillMaxSize()77 .systemBarsPadding()78 .padding(horizontal = 28.dp, vertical = 32.dp),79 verticalArrangement = Arrangement.Center,80 ) {81 when (step) {82 1 -> Step1(onCancel = onBack, onContinue = { step = 2 })83 2 -> Step2(entryCount = entryCount, onBack = { step = 1 }, onContinue = { step = 3 })84 else -> Step3(onDestroy = { vm.destroy(onDestroyed) })85 }86 }87 }88 }89}9091@Composable92private fun Step1(onCancel: () -> Unit, onContinue: () -> Unit) {93 Headline("destroy vault", 44.sp, peachColors.Breach)94 Spacer(Modifier.size(20.dp))95 Text(96 "This will permanently delete your vault and all saved passwords from this device. This cannot be undone.",97 color = peachColors.T1,98 style = PeachType.body,99 )100 Spacer(Modifier.size(28.dp))101 Row(modifier = Modifier.fillMaxWidth()) {102 GhostButton("Cancel", peachColors.T1, onCancel)103 Spacer(Modifier.width(12.dp))104 DangerButton("I understand, continue", enabled = true, onClick = onContinue)105 }106}107108@Composable109private fun Step2(entryCount: Int, onBack: () -> Unit, onContinue: () -> Unit) {110 Headline("are you sure?", 32.sp, peachColors.Breach)111 Spacer(Modifier.size(18.dp))112 Text(113 "You will lose access to all $entryCount saved passwords.",114 color = peachColors.T1,115 style = PeachType.body,116 )117 Spacer(Modifier.size(28.dp))118 Row(modifier = Modifier.fillMaxWidth()) {119 GhostButton("Go back", peachColors.T1, onBack)120 Spacer(Modifier.width(12.dp))121 DangerButton("Yes, destroy it", enabled = true, onClick = onContinue)122 }123}124125@Composable126private fun Step3(onDestroy: () -> Unit) {127 var typed by remember { mutableStateOf(TextFieldValue("")) }128 val accent = LocalPeachAccent.current129 val confirmed = typed.text == "DESTROY"130131 Headline("type DESTROY to confirm", 28.sp, peachColors.T0)132 Spacer(Modifier.size(22.dp))133 BasicTextField(134 value = typed,135 onValueChange = { typed = it },136 singleLine = true,137 textStyle = TextStyle(138 fontFamily = PeachType.Mono,139 fontSize = 18.sp,140 letterSpacing = 0.18.em,141 color = peachColors.T0,142 ),143 cursorBrush = SolidColor(accent),144 modifier = Modifier.fillMaxWidth(),145 )146 Spacer(Modifier.size(6.dp))147 Box(148 modifier = Modifier149 .fillMaxWidth()150 .size(1.dp)151 .background(peachColors.T3),152 )153 Spacer(Modifier.size(28.dp))154 DangerButton("Destroy vault", enabled = confirmed, onClick = onDestroy)155}156157@Composable158private fun Headline(text: String, fontSize: androidx.compose.ui.unit.TextUnit, color: Color) {159 Text(160 text,161 color = color,162 style = TextStyle(163 fontFamily = PeachType.Display,164 fontStyle = FontStyle.Italic,165 fontSize = fontSize,166 ),167 )168}169170@Composable171private fun GhostButton(label: String, color: Color, onClick: () -> Unit) {172 Box(173 modifier = Modifier174 .clip(RoundedCornerShape(999.dp))175 .border(1.dp, color.copy(alpha = 0.4f), RoundedCornerShape(999.dp))176 .clickable(onClick = onClick)177 .padding(horizontal = 18.dp, vertical = 11.dp),178 ) {179 Text(label, color = color, style = PeachType.sortPill.copy(fontSize = 11.sp))180 }181}182183@Composable184private fun DangerButton(label: String, enabled: Boolean, onClick: () -> Unit) {185 val alpha = if (enabled) 1f else 0.4f186 Box(187 modifier = Modifier188 .clip(RoundedCornerShape(999.dp))189 .background(peachColors.BreachBg.copy(alpha = peachColors.BreachBg.alpha * alpha))190 .border(1.dp, peachColors.Breach.copy(alpha = 0.55f * alpha), RoundedCornerShape(999.dp))191 .clickable(enabled = enabled, onClick = onClick)192 .padding(horizontal = 18.dp, vertical = 11.dp),193 ) {194 Text(195 label,196 color = peachColors.Breach.copy(alpha = alpha),197 style = PeachType.sortPill.copy(fontSize = 11.sp),198 )199 }200}