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

view file

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,

view file

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 }

view file

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 }

view file