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.RuneGridBackground39import 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 android.content.Context47import dagger.hilt.android.qualifiers.ApplicationContext48import com.peachpasswords.ui.onboarding.ONBOARD_KEY49import com.peachpasswords.ui.onboarding.ONBOARD_PREFS50import com.peachpasswords.sync.SyncFreshStartResetter51import 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 private val syncFreshStart: SyncFreshStartResetter,60 @ApplicationContext private val appContext: Context,61) : ViewModel() {62 val entryCount: Int get() = vault.getEntries().size6364 fun destroy(onDone: () -> Unit) = viewModelScope.launch {65 vault.destroyVault()66 syncFreshStart.reset()67 68 69 70 71 appContext.getSharedPreferences(ONBOARD_PREFS, Context.MODE_PRIVATE)72 .edit().remove(ONBOARD_KEY).apply()73 onDone()74 }75}7677@Composable78fun PanicScreen(79 onBack: () -> Unit,80 onDestroyed: () -> Unit,81 vm: PanicViewModel = hiltViewModel(),82) {83 var step by remember { mutableStateOf(1) }84 val entryCount = remember { vm.entryCount }8586 87 88 val accent = LocalPeachAccent.current89 val darkPalette = remember(accent) { peachDark(accent) }9091 CompositionLocalProvider(LocalPeachColors provides darkPalette) {92 93 94 Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0C0A06))) {95 96 97 98 if (step == 3) {99 RuneGridBackground(100 modifier = Modifier.fillMaxSize(),101 alpha = 0.22f,102 glowing = true,103 )104 }105 Column(106 modifier = Modifier107 .fillMaxSize()108 .systemBarsPadding()109 .padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.md),110 ) {111 PeachTopBar(112 title = "Panic",113 leading = {114 Icon(115 Icons.AutoMirrored.Rounded.ArrowBack,116 contentDescription = "back",117 tint = peachColors.T0,118 modifier = Modifier119 .size(22.dp)120 .clip(RoundedCornerShape(PeachSpacing.sm))121 .clickable(onClick = onBack),122 )123 },124 )125 Box(126 modifier = Modifier.fillMaxSize().padding(horizontal = PeachSpacing.sm),127 contentAlignment = Alignment.Center,128 ) {129 Column(verticalArrangement = Arrangement.Center) {130 when (step) {131 1 -> Step1(onCancel = onBack, onContinue = { step = 2 })132 2 -> Step2(entryCount = entryCount, onBack = { step = 1 }, onContinue = { step = 3 })133 else -> Step3(onBack = { step = 2 }, onDestroy = { vm.destroy(onDestroyed) })134 }135 }136 }137 }138 }139 }140}141142@Composable143private fun Step1(onCancel: () -> Unit, onContinue: () -> Unit) {144 Headline("destroy vault", 44.sp, peachColors.Breach)145 Spacer(Modifier.height(PeachSpacing.lg))146 Text(147 "This will permanently delete your vault and all saved passwords from this device. This cannot be undone.",148 color = peachColors.T1,149 style = PeachType.body,150 )151 Spacer(Modifier.height(PeachSpacing.xl))152 Column(153 modifier = Modifier.fillMaxWidth(),154 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),155 ) {156 GhostButton("Cancel", peachColors.T1, onCancel)157 DangerButton("I understand, continue", enabled = true, onClick = onContinue)158 }159}160161@Composable162private fun Step2(entryCount: Int, onBack: () -> Unit, onContinue: () -> Unit) {163 Headline("are you sure?", 32.sp, peachColors.Breach)164 Spacer(Modifier.height(PeachSpacing.lg))165 Text(166 "You will lose access to all $entryCount saved passwords.",167 color = peachColors.T1,168 style = PeachType.body,169 )170 Spacer(Modifier.height(PeachSpacing.xl))171 Column(172 modifier = Modifier.fillMaxWidth(),173 verticalArrangement = Arrangement.spacedBy(PeachSpacing.sm),174 ) {175 GhostButton("Go back", peachColors.T1, onBack)176 DangerButton("Yes, destroy it", enabled = true, onClick = onContinue)177 }178}179180@Composable181private fun Step3(onBack: () -> Unit, onDestroy: () -> Unit) {182 183 184 185 186 187 Column(modifier = Modifier.fillMaxSize()) {188 189 Box(190 modifier = Modifier191 .fillMaxWidth()192 .background(peachColors.Breach)193 .padding(vertical = 12.dp, horizontal = PeachSpacing.lg),194 contentAlignment = Alignment.Center,195 ) {196 Text(197 "THIS IS YOUR LAST CHANCE",198 color = peachColors.ShellTopSolid,199 style = PeachType.sortPill.copy(200 fontSize = 12.sp,201 fontStyle = FontStyle.Italic,202 ),203 )204 }205 Spacer(Modifier.height(PeachSpacing.lg))206 Text(207 "Your vault is about to be permanently destroyed.",208 color = peachColors.T0,209 style = PeachType.body,210 )211 Spacer(Modifier.height(PeachSpacing.lg))212 Text("There is no recovery.", color = peachColors.Breach, style = PeachType.body)213 Text("There is no support.", color = peachColors.Breach, style = PeachType.body)214 Text("There is no undo.", color = peachColors.Breach, style = PeachType.body)215 Spacer(Modifier.height(PeachSpacing.lg))216 217 Box(218 modifier = Modifier219 .fillMaxWidth()220 .height(1.dp)221 .background(peachColors.T3.copy(alpha = 0.25f)),222 )223 Spacer(Modifier.weight(1f))224 ChaosDestroyButton("DESTROY MY VAULT FOREVER", onClick = onDestroy)225 Spacer(Modifier.height(PeachSpacing.sm))226 Box(227 modifier = Modifier.fillMaxWidth(),228 contentAlignment = Alignment.Center,229 ) {230 Text(231 "No — take me back",232 color = peachColors.T2,233 style = PeachType.sortPill.copy(fontSize = 11.sp),234 modifier = Modifier235 .clip(RoundedCornerShape(999.dp))236 .clickable(onClick = onBack)237 .padding(horizontal = PeachSpacing.lg, vertical = 8.dp),238 )239 }240 }241}242243@Composable244private fun ChaosDestroyButton(label: String, onClick: () -> Unit) {245 Box(246 modifier = Modifier247 .fillMaxWidth()248 .clip(RoundedCornerShape(10.dp))249 .background(peachColors.Breach)250 .clickable(onClick = onClick)251 .padding(vertical = 14.dp),252 contentAlignment = Alignment.Center,253 ) {254 Text(255 label,256 color = peachColors.ShellTopSolid,257 style = PeachType.sortPill.copy(258 fontSize = 12.sp,259 fontStyle = FontStyle.Italic,260 ),261 )262 }263}264265@Composable266private fun Headline(text: String, fontSize: androidx.compose.ui.unit.TextUnit, color: Color) {267 Text(268 text,269 color = color,270 style = TextStyle(271 fontFamily = PeachType.Display,272 fontStyle = FontStyle.Italic,273 fontSize = fontSize,274 ),275 )276}277278@Composable279private fun GhostButton(label: String, color: Color, onClick: () -> Unit) {280 Box(281 modifier = Modifier282 .fillMaxWidth()283 .clip(RoundedCornerShape(999.dp))284 .border(1.dp, color.copy(alpha = 0.4f), RoundedCornerShape(999.dp))285 .clickable(onClick = onClick)286 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),287 contentAlignment = Alignment.Center,288 ) {289 Text(label, color = color, style = PeachType.sortPill.copy(fontSize = 11.sp))290 }291}292293@Composable294private fun DangerButton(label: String, enabled: Boolean, onClick: () -> Unit) {295 val alpha = if (enabled) 1f else 0.4f296 Box(297 modifier = Modifier298 .fillMaxWidth()299 .clip(RoundedCornerShape(999.dp))300 .background(peachColors.BreachBg.copy(alpha = peachColors.BreachBg.alpha * alpha))301 .border(1.dp, peachColors.Breach.copy(alpha = 0.55f * alpha), RoundedCornerShape(999.dp))302 .clickable(enabled = enabled, onClick = onClick)303 .padding(horizontal = PeachSpacing.lg, vertical = 11.dp),304 contentAlignment = Alignment.Center,305 ) {306 Text(307 label,308 color = peachColors.Breach.copy(alpha = alpha),309 style = PeachType.sortPill.copy(fontSize = 11.sp),310 )311 }312}