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
4 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 −0
app/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 {

view file

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 }

view file

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 

view file

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() {

view file