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.RuneGridBackground40import com.peachpasswords.ui.components.PeachTopBar41import com.peachpasswords.ui.theme.LocalPeachAccent42import com.peachpasswords.ui.theme.LocalPeachColors43import com.peachpasswords.ui.theme.PeachSpacing44import com.peachpasswords.ui.theme.peachColors45import com.peachpasswords.ui.theme.peachDark46import com.peachpasswords.ui.theme.PeachType47import android.content.Context48import dagger.hilt.android.qualifiers.ApplicationContext49import com.peachpasswords.ui.onboarding.ONBOARD_KEY50import com.peachpasswords.ui.onboarding.ONBOARD_PREFS51import com.peachpasswords.sync.SyncFreshStartResetter52import com.peachpasswords.vault.VaultRepository53import dagger.hilt.android.lifecycle.HiltViewModel54import kotlinx.coroutines.launch55import javax.inject.Inject5657@HiltViewModel58class PanicViewModel @Inject constructor(59 private val vault: VaultRepository,60 private val syncFreshStart: SyncFreshStartResetter,61 @ApplicationContext private val appContext: Context,62) : ViewModel() {63 val entryCount: Int get() = vault.getEntries().size6465 fun destroy(onDone: () -> Unit) = viewModelScope.launch {66 vault.destroyVault()67 syncFreshStart.reset()68 69 70 71 72 appContext.getSharedPreferences(ONBOARD_PREFS, Context.MODE_PRIVATE)73 .edit().remove(ONBOARD_KEY).apply()74 onDone()75 }76}7778@Composable79fun PanicScreen(80 onBack: () -> Unit,81 onDestroyed: () -> Unit,82 vm: PanicViewModel = hiltViewModel(),83) {84 var step by remember { mutableStateOf(1) }85 val entryCount = remember { vm.entryCount }8687 88 89 val accent = LocalPeachAccent.current90 val darkPalette = remember(accent) { peachDark(accent) }9192 CompositionLocalProvider(LocalPeachColors provides darkPalette) {93 94 95 Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0C0A06))) {96 97 98 99 if (step == 3) {100 RuneGridBackground(101 modifier = Modifier.fillMaxSize(),102 alpha = 0.22f,103 glowing = true,104 )105 }106 Column(107 modifier = Modifier108 .fillMaxSize()109 .systemBarsPadding()110 .padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.md),111 ) {112 PeachTopBar(113 title = "Panic",114 leading = {115 Icon(116 Icons.AutoMirrored.Rounded.ArrowBack,117 contentDescription = "back",118 tint = peachColors.T0,119 modifier = Modifier120 .size(22.dp)121 .clip(RoundedCornerShape(PeachSpacing.sm))122 .clickable(onClick = onBack),123 )124 },125 )126 Box(127 modifier = Modifier.fillMaxSize().padding(horizontal = PeachSpacing.sm),128 contentAlignment = Alignment.Center,129 ) {130 Column(verticalArrangement = Arrangement.Center) {131 when (step) {132 1 -> Step1(onCancel = onBack, onContinue = { step = 2 })133 2 -> Step2(entryCount = entryCount, onBack = { step = 1 }, onContinue = { step = 3 })134 else -> Step3(onDestroy = { vm.destroy(onDestroyed) })135 }136 }137 }138 }139 }140 }141}142143@Composable144private fun Step1(onCancel: () -> Unit, onContinue: () -> Unit) {145 Headline("destroy vault", 44.sp, peachColors.Breach)146 Spacer(Modifier.height(PeachSpacing.lg))147 Text(148 "This will permanently delete your vault and all saved passwords from this device. This cannot be undone.",149 color = peachColors.T1,150 style = PeachType.body,151 )152 Spacer(Modifier.height(PeachSpacing.xl))153 Column(154 modifier = Modifier.fillMaxWidth(),155 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),156 ) {157 GhostButton("Cancel", peachColors.T1, onCancel)158 DangerButton("I understand, continue", enabled = true, onClick = onContinue)159 }160}161162@Composable163private fun Step2(entryCount: Int, onBack: () -> Unit, onContinue: () -> Unit) {164 Headline("are you sure?", 32.sp, peachColors.Breach)165 Spacer(Modifier.height(PeachSpacing.lg))166 Text(167 "You will lose access to all $entryCount saved passwords.",168 color = peachColors.T1,169 style = PeachType.body,170 )171 Spacer(Modifier.height(PeachSpacing.xl))172 Column(173 modifier = Modifier.fillMaxWidth(),174 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),175 ) {176 GhostButton("Go back", peachColors.T1, onBack)177 DangerButton("Yes, destroy it", enabled = true, onClick = onContinue)178 }179}180181@Composable182private fun Step3(onDestroy: () -> Unit) {183 var typed by remember { mutableStateOf("") }184 val confirmed = typed == "DESTROY"185186 Headline("type DESTROY to confirm", 28.sp, peachColors.T0)187 Spacer(Modifier.height(PeachSpacing.lg))188 PeachField(189 label = "confirmation",190 value = typed,191 onValueChange = { typed = it },192 )193 Spacer(Modifier.height(PeachSpacing.xl))194 DangerButton("Destroy vault", enabled = confirmed, onClick = onDestroy)195}196197@Composable198private fun Headline(text: String, fontSize: androidx.compose.ui.unit.TextUnit, color: Color) {199 Text(200 text,201 color = color,202 style = TextStyle(203 fontFamily = PeachType.Display,204 fontStyle = FontStyle.Italic,205 fontSize = fontSize,206 ),207 )208}209210@Composable211private fun GhostButton(label: String, color: Color, onClick: () -> Unit) {212 Box(213 modifier = Modifier214 .fillMaxWidth()215 .clip(RoundedCornerShape(999.dp))216 .border(1.dp, color.copy(alpha = 0.4f), RoundedCornerShape(999.dp))217 .clickable(onClick = onClick)218 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),219 contentAlignment = Alignment.Center,220 ) {221 Text(label, color = color, style = PeachType.sortPill.copy(fontSize = 11.sp))222 }223}224225@Composable226private fun DangerButton(label: String, enabled: Boolean, onClick: () -> Unit) {227 val alpha = if (enabled) 1f else 0.4f228 Box(229 modifier = Modifier230 .fillMaxWidth()231 .clip(RoundedCornerShape(999.dp))232 .background(peachColors.BreachBg.copy(alpha = peachColors.BreachBg.alpha * alpha))233 .border(1.dp, peachColors.Breach.copy(alpha = 0.55f * alpha), RoundedCornerShape(999.dp))234 .clickable(enabled = enabled, onClick = onClick)235 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),236 contentAlignment = Alignment.Center,237 ) {238 Text(239 label,240 color = peachColors.Breach.copy(alpha = alpha),241 style = PeachType.sortPill.copy(fontSize = 11.sp),242 )243 }244}