15ca7bed330e
fix: Match browser add-entry identity suggestions (v2.3.12)
- time
- 2026-06-26 01:11 UTC · 4 months ago
- machine
- koh2
- parent
- d548c5f17d11
↓ peach-android-15ca7bed.tar.gzatlas at this save
files changed4 files
app/build.gradle+2 −2app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt+2 −1app/src/main/java/com/peachpasswords/ui/vault/EntryEditViewModel.kt+36 −6app/src/test/java/com/peachpasswords/ui/vault/EntryEditScreenTest.kt+26 −0app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616 applicationId "com.peachpasswords"1717 minSdk 261818 targetSdk 3519- versionCode 23110020- versionName "2.3.11"19+ versionCode 23120020+ versionName "2.3.12"2121 2222 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323 vectorDrawables {app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt+2 −1
@@ -60,6 +60,7 @@6060 ) {6161 val f by vm.form.collectAsState()6262 val usernameSuggestions by vm.usernameSuggestions.collectAsState()63+ val emailSuggestions by vm.emailSuggestions.collectAsState()6364 val accent = LocalPeachAccent.current6465 6566 // Ghost watermark fades in (0 → 0.03α over 300ms) once the title has any text.@@ -123,7 +124,7 @@123124 onRemove = vm::removeAdditionalUrl,124125 )125126 IdentitySuggestField("username", f.username, usernameSuggestions, vm::setUsername)126- IdentitySuggestField("email", f.email, usernameSuggestions, vm::setEmail)127+ IdentitySuggestField("email", f.email, emailSuggestions, vm::setEmail)127128 PasswordField(128129 value = f.password,129130 show = f.showPassword,app/src/main/java/com/peachpasswords/ui/vault/EntryEditViewModel.kt+36 −6
@@ -87,14 +87,13 @@8787 )8888 val form: StateFlow<EditForm> = _form.asStateFlow()8989 90- /**91- * Up to 4 previously-used identity values, frequency-sorted. Username and92- * email share one field in the Peach model, so both editor fields suggest93- * from "username".94- */90+ /** Browser AddView parity: field-specific identity suggestions, frequency-sorted. */9591 val usernameSuggestions: StateFlow<List<String>> =96- MutableStateFlow(vault.getDistinctFieldValues("username").take(4)).asStateFlow()92+ MutableStateFlow(identitySuggestionsFor(vault.getActiveEntries(), IdentityField.Username)).asStateFlow()9793 94+ val emailSuggestions: StateFlow<List<String>> =95+ MutableStateFlow(identitySuggestionsFor(vault.getActiveEntries(), IdentityField.Email)).asStateFlow()96+9897 fun setType(t: String) = _form.update { it.copy(type = t) }9998 10099 /**@@ -255,4 +254,35 @@255254 .onFailure { e -> _form.update { it.copy(saving = false, error = e.message ?: "save failed") } }256255 }257256 }257+}258+259+internal enum class IdentityField { Username, Email }260+261+internal fun identitySuggestionsFor(entries: List<VaultEntry>, field: IdentityField): List<String> {262+ val buckets = linkedMapOf<String, Pair<String, Int>>()263+264+ fun add(value: String) {265+ val trimmed = value.trim()266+ if (trimmed.isEmpty()) return267+ val key = trimmed.lowercase()268+ val current = buckets[key]269+ buckets[key] = (current?.first ?: trimmed) to ((current?.second ?: 0) + 1)270+ }271+272+ entries.filter { it.deletedAt == null }.forEach { entry ->273+ val username = entry.username?.trim().orEmpty()274+ val email = entry.email?.trim().orEmpty()275+ when (field) {276+ IdentityField.Username -> if (username.isNotEmpty() && !username.contains("@")) add(username)277+ IdentityField.Email -> {278+ if (username.contains("@")) add(username)279+ if (email.isNotEmpty()) add(email)280+ }281+ }282+ }283+284+ return buckets.values285+ .sortedWith(compareByDescending<Pair<String, Int>> { it.second }.thenBy { it.first.lowercase() })286+ .map { it.first }287+ .take(8)258288 }app/src/test/java/com/peachpasswords/ui/vault/EntryEditScreenTest.kt+26 −0
@@ -1,5 +1,6 @@11 package com.peachpasswords.ui.vault22 3+import com.peachpasswords.vault.VaultEntry34 import org.junit.Assert.assertEquals45 import org.junit.Assert.assertFalse56 import org.junit.Assert.assertTrue@@ -70,6 +71,21 @@7071 assertFalse(shouldShowAddNotesLink("login", revealed = false, notes = "remember this"))7172 }7273 74+ @Test fun identity_suggestions_split_username_and_email_like_browser() {75+ val entries = listOf(76+ identityEntry("a", username = "june", email = "[email protected]"),77+ identityEntry("b", username = "june", email = null),78+ identityEntry("c", username = "[email protected]", email = "[email protected]"),79+ identityEntry("d", username = "[email protected]", email = null),80+ )81+82+ assertEquals(listOf("june"), identitySuggestionsFor(entries, IdentityField.Username))83+ assertEquals(84+ listOf("[email protected]", "[email protected]", "[email protected]"),85+ identitySuggestionsFor(entries, IdentityField.Email),86+ )87+ }88+7389 // ── Card field formatting (CardFieldUtils, used live by setCardNumber etc.) ─7490 7591 @Test fun new_card_number_auto_formats() {@@ -92,4 +108,14 @@92108 // Past year rejected.93109 assertFalse(CardFieldUtils.isExpiryValid("12/24", currentYear2 = 26, currentMonth = 6))94110 }111+112+ private fun identityEntry(id: String, username: String?, email: String?) = VaultEntry(113+ id = id,114+ title = id,115+ type = "login",116+ username = username,117+ email = email,118+ createdAt = 0,119+ modifiedAt = 0,120+ )95121 }