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.vault.VaultRepository52import dagger.hilt.android.lifecycle.HiltViewModel53import kotlinx.coroutines.launch54import javax.inject.Inject5556@HiltViewModel57class PanicViewModel @Inject constructor(58 private val vault: VaultRepository,59 @ApplicationContext private val appContext: Context,60) : ViewModel() {61 val entryCount: Int get() = vault.getEntries().size6263 fun destroy(onDone: () -> Unit) = viewModelScope.launch {64 vault.destroyVault()65 66 67 68 69 appContext.getSharedPreferences(ONBOARD_PREFS, Context.MODE_PRIVATE)70 .edit().remove(ONBOARD_KEY).apply()71 onDone()72 }73}7475@Composable76fun PanicScreen(77 onBack: () -> Unit,78 onDestroyed: () -> Unit,79 vm: PanicViewModel = hiltViewModel(),80) {81 var step by remember { mutableStateOf(1) }82 val entryCount = remember { vm.entryCount }8384 85 86 val accent = LocalPeachAccent.current87 val darkPalette = remember(accent) { peachDark(accent) }8889 CompositionLocalProvider(LocalPeachColors provides darkPalette) {90 91 92 Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0C0A06))) {93 94 95 96 if (step == 3) {97 RuneGridBackground(98 modifier = Modifier.fillMaxSize(),99 alpha = 0.22f,100 glowing = true,101 )102 }103 Column(104 modifier = Modifier105 .fillMaxSize()106 .systemBarsPadding()107 .padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.md),108 ) {109 PeachTopBar(110 title = "Panic",111 leading = {112 Icon(113 Icons.AutoMirrored.Rounded.ArrowBack,114 contentDescription = "back",115 tint = peachColors.T0,116 modifier = Modifier117 .size(22.dp)118 .clip(RoundedCornerShape(PeachSpacing.sm))119 .clickable(onClick = onBack),120 )121 },122 )123 Box(124 modifier = Modifier.fillMaxSize().padding(horizontal = PeachSpacing.sm),125 contentAlignment = Alignment.Center,126 ) {127 Column(verticalArrangement = Arrangement.Center) {128 when (step) {129 1 -> Step1(onCancel = onBack, onContinue = { step = 2 })130 2 -> Step2(entryCount = entryCount, onBack = { step = 1 }, onContinue = { step = 3 })131 else -> Step3(onDestroy = { vm.destroy(onDestroyed) })132 }133 }134 }135 }136 }137 }138}139140@Composable141private fun Step1(onCancel: () -> Unit, onContinue: () -> Unit) {142 Headline("destroy vault", 44.sp, peachColors.Breach)143 Spacer(Modifier.height(PeachSpacing.lg))144 Text(145 "This will permanently delete your vault and all saved passwords from this device. This cannot be undone.",146 color = peachColors.T1,147 style = PeachType.body,148 )149 Spacer(Modifier.height(PeachSpacing.xl))150 Column(151 modifier = Modifier.fillMaxWidth(),152 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),153 ) {154 GhostButton("Cancel", peachColors.T1, onCancel)155 DangerButton("I understand, continue", enabled = true, onClick = onContinue)156 }157}158159@Composable160private fun Step2(entryCount: Int, onBack: () -> Unit, onContinue: () -> Unit) {161 Headline("are you sure?", 32.sp, peachColors.Breach)162 Spacer(Modifier.height(PeachSpacing.lg))163 Text(164 "You will lose access to all $entryCount saved passwords.",165 color = peachColors.T1,166 style = PeachType.body,167 )168 Spacer(Modifier.height(PeachSpacing.xl))169 Column(170 modifier = Modifier.fillMaxWidth(),171 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),172 ) {173 GhostButton("Go back", peachColors.T1, onBack)174 DangerButton("Yes, destroy it", enabled = true, onClick = onContinue)175 }176}177178@Composable179private fun Step3(onDestroy: () -> Unit) {180 var typed by remember { mutableStateOf("") }181 val confirmed = typed == "DESTROY"182183 Headline("type DESTROY to confirm", 28.sp, peachColors.T0)184 Spacer(Modifier.height(PeachSpacing.lg))185 PeachField(186 label = "confirmation",187 value = typed,188 onValueChange = { typed = it },189 )190 Spacer(Modifier.height(PeachSpacing.xl))191 DangerButton("Destroy vault", enabled = confirmed, onClick = onDestroy)192}193194@Composable195private fun Headline(text: String, fontSize: androidx.compose.ui.unit.TextUnit, color: Color) {196 Text(197 text,198 color = color,199 style = TextStyle(200 fontFamily = PeachType.Display,201 fontStyle = FontStyle.Italic,202 fontSize = fontSize,203 ),204 )205}206207@Composable208private fun GhostButton(label: String, color: Color, onClick: () -> Unit) {209 Box(210 modifier = Modifier211 .fillMaxWidth()212 .clip(RoundedCornerShape(999.dp))213 .border(1.dp, color.copy(alpha = 0.4f), RoundedCornerShape(999.dp))214 .clickable(onClick = onClick)215 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),216 contentAlignment = Alignment.Center,217 ) {218 Text(label, color = color, style = PeachType.sortPill.copy(fontSize = 11.sp))219 }220}221222@Composable223private fun DangerButton(label: String, enabled: Boolean, onClick: () -> Unit) {224 val alpha = if (enabled) 1f else 0.4f225 Box(226 modifier = Modifier227 .fillMaxWidth()228 .clip(RoundedCornerShape(999.dp))229 .background(peachColors.BreachBg.copy(alpha = peachColors.BreachBg.alpha * alpha))230 .border(1.dp, peachColors.Breach.copy(alpha = 0.55f * alpha), RoundedCornerShape(999.dp))231 .clickable(enabled = enabled, onClick = onClick)232 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),233 contentAlignment = Alignment.Center,234 ) {235 Text(236 label,237 color = peachColors.Breach.copy(alpha = alpha),238 style = PeachType.sortPill.copy(fontSize = 11.sp),239 )240 }241}