1package com.peachpasswords.ui.vault23import 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.fillMaxWidth12import androidx.compose.foundation.layout.height13import androidx.compose.foundation.layout.padding14import androidx.compose.foundation.layout.width15import androidx.compose.foundation.shape.RoundedCornerShape16import androidx.compose.material3.Text17import androidx.compose.runtime.Composable18import androidx.compose.runtime.getValue19import androidx.compose.runtime.mutableStateOf20import androidx.compose.runtime.remember21import androidx.compose.runtime.setValue22import androidx.compose.ui.Alignment23import androidx.compose.ui.Modifier24import androidx.compose.ui.draw.clip25import androidx.compose.ui.text.font.FontStyle26import androidx.compose.ui.unit.dp27import androidx.compose.ui.unit.sp28import com.peachpasswords.ui.theme.LocalPeachAccent29import com.peachpasswords.ui.theme.PeachSpacing30import com.peachpasswords.ui.theme.peachColors31import com.peachpasswords.ui.theme.PeachType32import com.peachpasswords.vault.PasswordHistoryItem333435363738394041424344454647@Composable48internal fun PasswordHistorySection(49 history: List<PasswordHistoryItem>,50 expanded: Boolean,51 onToggle: () -> Unit,52 onRestore: (String) -> Unit,53 onCopy: (String) -> Unit,54) {55 PasswordHistoryToggle(56 count = history.size,57 expanded = expanded,58 onToggle = onToggle,59 modifier = Modifier.fillMaxWidth().padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.sm),60 )61 if (expanded) {62 Box(Modifier.fillMaxWidth().height(1.dp).background(peachColors.T3))63 Column(Modifier.fillMaxWidth().padding(horizontal = PeachSpacing.lg, vertical = PeachSpacing.md)) {64 PasswordHistoryList(history = history, onRestore = onRestore, onCopy = onCopy)65 }66 }67}686970717273@Composable74internal fun PasswordHistoryToggle(75 count: Int,76 expanded: Boolean,77 onToggle: () -> Unit,78 modifier: Modifier = Modifier,79) {80 val accent = LocalPeachAccent.current81 val label = if (count == 0) "↺ history" else "↺ history ($count)"82 Text(83 if (expanded) "↺ hide history" else label,84 color = accent.copy(alpha = 0.7f),85 fontFamily = PeachType.Display,86 fontStyle = FontStyle.Italic,87 fontSize = 15.sp,88 modifier = modifier.clip(RoundedCornerShape(6.dp)).clickable(onClick = onToggle).padding(vertical = 4.dp),89 )90}919293949596@Composable97internal fun PasswordHistoryList(98 history: List<PasswordHistoryItem>,99 onRestore: (String) -> Unit,100 onCopy: (String) -> Unit,101) {102 val accent = LocalPeachAccent.current103 if (history.isEmpty()) {104 Text(105 "No earlier passwords yet. When you change this password, the old one is saved here so you can copy or restore it.",106 color = peachColors.T2,107 fontFamily = PeachType.Mono,108 fontSize = 10.sp,109 modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),110 )111 } else {112 Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {113 history.forEach { item -> HistoryRow(item, onRestore, onCopy) }114 }115 }116}117118@Composable119private fun HistoryRow(120 item: PasswordHistoryItem,121 onRestore: (String) -> Unit,122 onCopy: (String) -> Unit,123) {124 val accent = LocalPeachAccent.current125 var revealed by remember { mutableStateOf(false) }126 Row(127 Modifier.fillMaxWidth().clip(RoundedCornerShape(8.dp))128 .border(1.dp, accent.copy(alpha = 0.12f), RoundedCornerShape(8.dp))129 .background(accent.copy(alpha = 0.06f)).padding(horizontal = 10.dp, vertical = 8.dp),130 verticalAlignment = Alignment.CenterVertically,131 horizontalArrangement = Arrangement.SpaceBetween,132 ) {133 Column(Modifier.weight(1f)) {134 Text(135 if (revealed) item.password else "••••••••••••",136 color = peachColors.T0, fontFamily = PeachType.Mono, fontSize = 12.sp, maxLines = 1,137 )138 Text(139 timeSince(item.changedAt),140 color = accent.copy(alpha = 0.55f), fontFamily = PeachType.Mono, fontSize = 8.sp,141 )142 }143 Spacer(Modifier.width(6.dp))144 HistoryAction(if (revealed) "hide" else "show") { revealed = !revealed }145 HistoryAction("copy") { onCopy(item.password) }146 HistoryAction("use") { onRestore(item.password) }147 }148}149150@Composable151private fun HistoryAction(label: String, onClick: () -> Unit) {152 Text(153 label, color = LocalPeachAccent.current.copy(alpha = 0.7f),154 fontFamily = PeachType.Mono, fontSize = 9.sp,155 modifier = Modifier.clip(RoundedCornerShape(5.dp)).clickable(onClick = onClick).padding(horizontal = 5.dp, vertical = 3.dp),156 )157}158159160private fun timeSince(whenMs: Long): String {161 val secs = ((System.currentTimeMillis() - whenMs) / 1000).coerceAtLeast(0)162 if (secs < 60) return "just now"163 val mins = secs / 60164 if (mins < 60) return "${mins}m ago"165 val hours = mins / 60166 if (hours < 24) return "${hours}h ago"167 val days = hours / 24168 if (days < 30) return "${days}d ago"169 val months = days / 30170 if (months < 12) return "${months}mo ago"171 return "${months / 12}y ago"172}