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.Spacer10import androidx.compose.foundation.layout.fillMaxSize11import androidx.compose.foundation.layout.fillMaxWidth12import androidx.compose.foundation.layout.height13import androidx.compose.foundation.layout.padding14import androidx.compose.foundation.layout.size15import androidx.compose.foundation.layout.systemBarsPadding16import androidx.compose.foundation.shape.RoundedCornerShape17import androidx.compose.material.icons.Icons18import androidx.compose.material.icons.automirrored.rounded.ArrowBack19import androidx.compose.material3.Icon20import androidx.compose.material3.Text21import androidx.compose.runtime.Composable22import androidx.compose.runtime.CompositionLocalProvider23import androidx.compose.runtime.getValue24import androidx.compose.runtime.mutableStateOf25import androidx.compose.runtime.remember26import androidx.compose.runtime.setValue27import androidx.compose.ui.Alignment28import androidx.compose.ui.Modifier29import androidx.compose.ui.draw.clip30import androidx.compose.ui.graphics.Color31import androidx.compose.ui.text.TextStyle32import androidx.compose.ui.text.font.FontStyle33import androidx.compose.ui.unit.sp34import androidx.compose.ui.unit.dp35import androidx.hilt.navigation.compose.hiltViewModel36import androidx.lifecycle.ViewModel37import androidx.lifecycle.viewModelScope38import com.peachpasswords.ui.components.PeachField39import com.peachpasswords.ui.components.PeachTopBar40import com.peachpasswords.ui.theme.LocalPeachAccent41import com.peachpasswords.ui.theme.LocalPeachColors42import com.peachpasswords.ui.theme.PeachSpacing43import com.peachpasswords.ui.theme.peachColors44import com.peachpasswords.ui.theme.peachDark45import com.peachpasswords.ui.theme.PeachType46import com.peachpasswords.vault.VaultRepository47import dagger.hilt.android.lifecycle.HiltViewModel48import kotlinx.coroutines.launch49import javax.inject.Inject5051@HiltViewModel52class PanicViewModel @Inject constructor(private val vault: VaultRepository) : ViewModel() {53 val entryCount: Int get() = vault.getEntries().size54 fun destroy(onDone: () -> Unit) = viewModelScope.launch { vault.destroyVault(); onDone() }55}5657@Composable58fun PanicScreen(59 onBack: () -> Unit,60 onDestroyed: () -> Unit,61 vm: PanicViewModel = hiltViewModel(),62) {63 var step by remember { mutableStateOf(1) }64 val entryCount = remember { vm.entryCount }6566 67 68 val accent = LocalPeachAccent.current69 val darkPalette = remember(accent) { peachDark(accent) }7071 CompositionLocalProvider(LocalPeachColors provides darkPalette) {72 73 74 Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0C0A06))) {75 Column(76 modifier = Modifier77 .fillMaxSize()78 .systemBarsPadding()79 .padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.md),80 ) {81 PeachTopBar(82 title = "Panic",83 leading = {84 Icon(85 Icons.AutoMirrored.Rounded.ArrowBack,86 contentDescription = "back",87 tint = peachColors.T0,88 modifier = Modifier89 .size(22.dp)90 .clip(RoundedCornerShape(PeachSpacing.sm))91 .clickable(onClick = onBack),92 )93 },94 )95 Box(96 modifier = Modifier.fillMaxSize().padding(horizontal = PeachSpacing.sm),97 contentAlignment = Alignment.Center,98 ) {99 Column(verticalArrangement = Arrangement.Center) {100 when (step) {101 1 -> Step1(onCancel = onBack, onContinue = { step = 2 })102 2 -> Step2(entryCount = entryCount, onBack = { step = 1 }, onContinue = { step = 3 })103 else -> Step3(onDestroy = { vm.destroy(onDestroyed) })104 }105 }106 }107 }108 }109 }110}111112@Composable113private fun Step1(onCancel: () -> Unit, onContinue: () -> Unit) {114 Headline("destroy vault", 44.sp, peachColors.Breach)115 Spacer(Modifier.height(PeachSpacing.lg))116 Text(117 "This will permanently delete your vault and all saved passwords from this device. This cannot be undone.",118 color = peachColors.T1,119 style = PeachType.body,120 )121 Spacer(Modifier.height(PeachSpacing.xl))122 Column(123 modifier = Modifier.fillMaxWidth(),124 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),125 ) {126 GhostButton("Cancel", peachColors.T1, onCancel)127 DangerButton("I understand, continue", enabled = true, onClick = onContinue)128 }129}130131@Composable132private fun Step2(entryCount: Int, onBack: () -> Unit, onContinue: () -> Unit) {133 Headline("are you sure?", 32.sp, peachColors.Breach)134 Spacer(Modifier.height(PeachSpacing.lg))135 Text(136 "You will lose access to all $entryCount saved passwords.",137 color = peachColors.T1,138 style = PeachType.body,139 )140 Spacer(Modifier.height(PeachSpacing.xl))141 Column(142 modifier = Modifier.fillMaxWidth(),143 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),144 ) {145 GhostButton("Go back", peachColors.T1, onBack)146 DangerButton("Yes, destroy it", enabled = true, onClick = onContinue)147 }148}149150@Composable151private fun Step3(onDestroy: () -> Unit) {152 var typed by remember { mutableStateOf("") }153 val confirmed = typed == "DESTROY"154155 Headline("type DESTROY to confirm", 28.sp, peachColors.T0)156 Spacer(Modifier.height(PeachSpacing.lg))157 PeachField(158 label = "confirmation",159 value = typed,160 onValueChange = { typed = it },161 )162 Spacer(Modifier.height(PeachSpacing.xl))163 DangerButton("Destroy vault", enabled = confirmed, onClick = onDestroy)164}165166@Composable167private fun Headline(text: String, fontSize: androidx.compose.ui.unit.TextUnit, color: Color) {168 Text(169 text,170 color = color,171 style = TextStyle(172 fontFamily = PeachType.Display,173 fontStyle = FontStyle.Italic,174 fontSize = fontSize,175 ),176 )177}178179@Composable180private fun GhostButton(label: String, color: Color, onClick: () -> Unit) {181 Box(182 modifier = Modifier183 .fillMaxWidth()184 .clip(RoundedCornerShape(999.dp))185 .border(1.dp, color.copy(alpha = 0.4f), RoundedCornerShape(999.dp))186 .clickable(onClick = onClick)187 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),188 contentAlignment = Alignment.Center,189 ) {190 Text(label, color = color, style = PeachType.sortPill.copy(fontSize = 11.sp))191 }192}193194@Composable195private fun DangerButton(label: String, enabled: Boolean, onClick: () -> Unit) {196 val alpha = if (enabled) 1f else 0.4f197 Box(198 modifier = Modifier199 .fillMaxWidth()200 .clip(RoundedCornerShape(999.dp))201 .background(peachColors.BreachBg.copy(alpha = peachColors.BreachBg.alpha * alpha))202 .border(1.dp, peachColors.Breach.copy(alpha = 0.55f * alpha), RoundedCornerShape(999.dp))203 .clickable(enabled = enabled, onClick = onClick)204 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),205 contentAlignment = Alignment.Center,206 ) {207 Text(208 label,209 color = peachColors.Breach.copy(alpha = alpha),210 style = PeachType.sortPill.copy(fontSize = 11.sp),211 )212 }213}