610b588ac078
fix: Match browser add-entry generator feel (v2.3.13)
- time
- 2026-06-26 01:16 UTC · 4 months ago
- machine
- koh2
- parent
- 15ca7bed330e
↓ peach-android-610b588a.tar.gzatlas at this save
files changed4 files
app/build.gradle+2 −2app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt+46 −3app/src/main/java/com/peachpasswords/ui/vault/EntryEditViewModel.kt+46 −19app/src/test/java/com/peachpasswords/ui/vault/EntryEditScreenTest.kt+6 −0app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616 applicationId "com.peachpasswords"1717 minSdk 261818 targetSdk 3519- versionCode 23120020- versionName "2.3.12"19+ versionCode 23130020+ versionName "2.3.13"2121 2222 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323 vectorDrawables {app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt+46 −3
@@ -38,6 +38,7 @@3838 import androidx.compose.ui.text.font.FontWeight3939 import androidx.compose.ui.text.input.PasswordVisualTransformation4040 import androidx.compose.ui.text.input.VisualTransformation41+import androidx.compose.ui.text.style.TextOverflow4142 import androidx.compose.ui.unit.dp4243 import androidx.compose.ui.unit.em4344 import androidx.compose.ui.unit.sp@@ -128,6 +129,7 @@128129 PasswordField(129130 value = f.password,130131 show = f.showPassword,132+ generatorOpen = f.generatorOpen,131133 onChange = vm::setPassword,132134 onToggleShow = vm::toggleShowPassword,133135 onToggleGenerator = vm::toggleGenerator,@@ -143,6 +145,7 @@143145 onNumbers = vm::setGenNumbers, onSymbols = vm::setGenSymbols,144146 onGenerate = vm::generate,145147 onCopy = vm::copyGenerated,148+ onUse = vm::useGeneratedPassword,146149 )147150 PasswordHistorySection(148151 history = f.history,@@ -314,6 +317,7 @@314317 private fun PasswordField(315318 value: String,316319 show: Boolean,320+ generatorOpen: Boolean,317321 onChange: (String) -> Unit,318322 onToggleShow: () -> Unit,319323 onToggleGenerator: () -> Unit,@@ -327,12 +331,15 @@327331 Text(if (show) "hide" else "show", color = LocalPeachAccent.current,328332 fontFamily = PeachType.Mono, fontSize = 9.sp,329333 modifier = Modifier.clip(RoundedCornerShape(6.dp)).clickable(onClick = onToggleShow).padding(6.dp))330- Text("⟳", color = LocalPeachAccent.current, fontSize = 16.sp,334+ Text(generatorToggleLabel(generatorOpen), color = LocalPeachAccent.current,335+ fontFamily = PeachType.Mono, fontSize = 9.sp,331336 modifier = Modifier.clip(RoundedCornerShape(6.dp)).clickable(onClick = onToggleGenerator).padding(6.dp))332337 },333338 )334339 }335340 341+internal fun generatorToggleLabel(open: Boolean): String = if (open) "hide generator" else "generate"342+336343 @Composable337344 private fun GeneratorDrawer(338345 open: Boolean,@@ -345,9 +352,10 @@345352 onNumbers: (Boolean) -> Unit, onSymbols: (Boolean) -> Unit,346353 onGenerate: () -> Unit,347354 onCopy: () -> Unit,355+ onUse: () -> Unit,348356 ) {349357 val height by animateDpAsState(350- targetValue = if (open) 234.dp else 0.dp,358+ targetValue = if (open) 326.dp else 0.dp,351359 animationSpec = tween(320, easing = PeachMotion.Spring),352360 label = "gen-drawer",353361 )@@ -362,6 +370,8 @@362370 onLength = onLength,363371 onUpper = onUpper, onLower = onLower, onNumbers = onNumbers, onSymbols = onSymbols,364372 onGenerate = onGenerate, onCopy = onCopy,373+ showOutput = true,374+ onUse = onUse,365375 )366376 }367377 if (open) Spacer(Modifier.height(14.dp))@@ -382,9 +392,29 @@382392 onNumbers: (Boolean) -> Unit, onSymbols: (Boolean) -> Unit,383393 onGenerate: () -> Unit,384394 onCopy: () -> Unit,395+ showOutput: Boolean = false,396+ onUse: (() -> Unit)? = null,385397 ) {386398 val accent = LocalPeachAccent.current387399 Column(Modifier.fillMaxWidth().padding(16.dp)) {400+ if (showOutput) {401+ Box(402+ Modifier.fillMaxWidth().clip(RoundedCornerShape(10.dp))403+ .border(1.dp, accent.copy(alpha = 0.45f), RoundedCornerShape(10.dp))404+ .background(accent.copy(alpha = 0.08f))405+ .padding(horizontal = 12.dp, vertical = 10.dp),406+ ) {407+ Text(408+ text = password.ifEmpty { "tap generate" },409+ color = if (password.isEmpty()) peachColors.T2 else peachColors.T0,410+ fontFamily = PeachType.Mono,411+ fontSize = 11.sp,412+ maxLines = 2,413+ overflow = TextOverflow.Ellipsis,414+ )415+ }416+ Spacer(Modifier.height(10.dp))417+ }388418 Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {389419 Text("GENERATOR", color = peachColors.T1, fontFamily = PeachType.Mono, fontSize = 8.sp)390420 Text("length $length", color = accent, fontFamily = PeachType.Mono, fontSize = 9.sp)@@ -412,10 +442,23 @@412442 .clickable(onClick = onGenerate).padding(vertical = 12.dp),413443 contentAlignment = Alignment.Center,414444 ) {415- Text("generate", color = accent, fontFamily = PeachType.Mono, fontSize = 12.sp)445+ Text(if (password.isEmpty()) "generate" else "regenerate", color = accent, fontFamily = PeachType.Mono, fontSize = 12.sp)416446 }417447 Spacer(Modifier.width(8.dp))418448 CopyButton(copied = copied, onCopy = { if (password.isNotEmpty()) onCopy() })449+ }450+ onUse?.let {451+ Spacer(Modifier.height(10.dp))452+ Box(453+ Modifier.fillMaxWidth().clip(RoundedCornerShape(10.dp))454+ .border(1.dp, accent.copy(alpha = 0.5f), RoundedCornerShape(10.dp))455+ .background(accent.copy(alpha = 0.14f))456+ .clickable(enabled = password.isNotEmpty(), onClick = it)457+ .padding(vertical = 12.dp),458+ contentAlignment = Alignment.Center,459+ ) {460+ Text("use", color = if (password.isEmpty()) accent.copy(alpha = 0.45f) else accent, fontFamily = PeachType.Mono, fontSize = 12.sp)461+ }419462 }420463 }421464 }app/src/main/java/com/peachpasswords/ui/vault/EntryEditViewModel.kt+46 −19
@@ -40,7 +40,7 @@4040 val genCopied: Boolean = false,4141 val history: List<PasswordHistoryItem> = emptyList(),4242 val showHistory: Boolean = false,43- val genLength: Int = 20,43+ val genLength: Int = BROWSER_ADD_GENERATOR_LENGTH,4444 val genUpper: Boolean = true,4545 val genLower: Boolean = true,4646 val genNumbers: Boolean = true,@@ -83,7 +83,7 @@8383 showTotp = !it.totp.isNullOrBlank(),8484 showNotes = !it.notes.isNullOrBlank(),8585 )86- } ?: EditForm(),86+ } ?: EditForm(password = generateEditPassword(BROWSER_ADD_GENERATOR_LENGTH)),8787 )8888 val form: StateFlow<EditForm> = _form.asStateFlow()8989 @@ -148,7 +148,11 @@148148 fun removeTag(tag: String) = _form.update { it.copy(tags = it.tags - tag) }149149 150150 fun toggleShowPassword() = _form.update { it.copy(showPassword = !it.showPassword) }151- fun toggleGenerator() = _form.update { it.copy(generatorOpen = !it.generatorOpen, showHistory = false) }151+ fun toggleGenerator() = _form.update { form ->152+ val opening = !form.generatorOpen153+ val next = form.copy(generatorOpen = opening, showHistory = false)154+ if (opening && next.password.isBlank()) next.withGeneratedPassword() else next155+ }152156 fun toggleHistory() = _form.update { it.copy(showHistory = !it.showHistory, generatorOpen = false) }153157 154158 /** Restore a past password into the editor; the save flow records the swap. */@@ -170,26 +174,18 @@170174 fun copyHistoryPassword(pw: String) {171175 PeachClipboard.copy(appContext, "peach", pw)172176 }173- fun setGenLength(n: Int) = _form.update { it.copy(genLength = n) }174- fun setGenUpper(b: Boolean) = _form.update { it.copy(genUpper = b) }175- fun setGenLower(b: Boolean) = _form.update { it.copy(genLower = b) }176- fun setGenNumbers(b: Boolean) = _form.update { it.copy(genNumbers = b) }177- fun setGenSymbols(b: Boolean) = _form.update { it.copy(genSymbols = b) }177+ fun setGenLength(n: Int) = _form.update { it.copy(genLength = n.coerceIn(8, 128)).withGeneratedPassword() }178+ fun setGenUpper(b: Boolean) = _form.update { it.copy(genUpper = b).withGeneratedPassword() }179+ fun setGenLower(b: Boolean) = _form.update { it.copy(genLower = b).withGeneratedPassword() }180+ fun setGenNumbers(b: Boolean) = _form.update { it.copy(genNumbers = b).withGeneratedPassword() }181+ fun setGenSymbols(b: Boolean) = _form.update { it.copy(genSymbols = b).withGeneratedPassword() }178182 179183 fun generate() {180- val f = _form.value181- val pw = PasswordGenerator.generate(182- PasswordGenerator.Options(183- length = f.genLength,184- includeUppercase = f.genUpper,185- includeLowercase = f.genLower,186- includeNumbers = f.genNumbers,187- includeSymbols = f.genSymbols,188- ),189- )190- _form.update { it.copy(password = pw, showPassword = true) }184+ _form.update { it.withGeneratedPassword() }191185 }192186 187+ fun useGeneratedPassword() = _form.update { it.copy(generatorOpen = false, showPassword = false) }188+193189 fun save(onDone: () -> Unit) {194190 val f = _form.value195191 if (f.saving) return@@ -255,6 +251,37 @@255251 }256252 }257253 }254+255+internal const val BROWSER_ADD_GENERATOR_LENGTH = 64256+257+private fun EditForm.withGeneratedPassword(): EditForm =258+ copy(259+ password = generateEditPassword(260+ length = genLength,261+ upper = genUpper,262+ lower = genLower,263+ numbers = genNumbers,264+ symbols = genSymbols,265+ ),266+ showPassword = true,267+ )268+269+private fun generateEditPassword(270+ length: Int,271+ upper: Boolean = true,272+ lower: Boolean = true,273+ numbers: Boolean = true,274+ symbols: Boolean = true,275+): String =276+ PasswordGenerator.generate(277+ PasswordGenerator.Options(278+ length = length,279+ includeUppercase = upper,280+ includeLowercase = lower,281+ includeNumbers = numbers,282+ includeSymbols = symbols,283+ ),284+ )258285 259286 internal enum class IdentityField { Username, Email }260287 app/src/test/java/com/peachpasswords/ui/vault/EntryEditScreenTest.kt+6 −0
@@ -86,6 +86,12 @@8686 )8787 }8888 89+ @Test fun generator_defaults_and_toggle_label_match_browser_add_view() {90+ assertEquals(64, BROWSER_ADD_GENERATOR_LENGTH)91+ assertEquals("generate", generatorToggleLabel(open = false))92+ assertEquals("hide generator", generatorToggleLabel(open = true))93+ }94+8995 // ── Card field formatting (CardFieldUtils, used live by setCardNumber etc.) ─9096 9197 @Test fun new_card_number_auto_formats() {