4969c80c9f49

feat: allow-screenshots toggle, search bar border removal, deck gap reduction (v2.9.31)

- Add allowScreenshots preference to VaultSettings with toggle in Protection card - Refactor SecureWindowPolicy from permanent installRelease to reactive setUserSecure - MainActivity applies screenshot preference via SideEffect; autofill/passkey stay hardcoded secure - Remove .border() from vault list SearchBar (was clipping text on scroll) - Reduce DECK_TO_VERSION_GAP from 6dp to 2dp (70% reduction, fixes Pro card button clipping)

time
2026-08-02 01:00 UTC · 2 months ago
machine
koh2
parent
e064cc9e34e5
10 files VERSION+1 −1app/src/main/java/com/peachpasswords/MainActivity.kt+9 −5app/src/main/java/com/peachpasswords/autofill/AutofillEntryPickerActivity.kt+1 −1app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt+1 −1app/src/main/java/com/peachpasswords/ui/components/SecureWindowPolicy.kt+17 −15app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+4 −0app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+1 −1app/src/main/java/com/peachpasswords/ui/settings/SettingsViewModel.kt+1 −0app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+0 −1app/src/main/java/com/peachpasswords/vault/VaultSettings.kt+1 −0
VERSION+1 −1
@@ -1 +1 @@1-2.9.301+2.9.31

view file

app/src/main/java/com/peachpasswords/MainActivity.kt+9 −5
@@ -80,13 +80,12 @@8080 8181         enableEdgeToEdge()8282 83-        // Block screenshots / recents thumbnails of the vault in release builds.83+        // Default to FLAG_SECURE immediately (before Compose draws) so there is84+        // never an unprotected flash. The user's "allow screenshots" preference85+        // is applied reactively inside setContent once settings are available.8486         if (!BuildConfig.DEBUG) {85-            SecureWindowPolicy.installRelease(window)87+            SecureWindowPolicy.setUserSecure(window, true)8688         } else {87-            // QA-only, debug-build-only: `adb shell am start -n <pkg>/.MainActivity88-            // --ez qa_disable_secure_flag true` lets automated visual QA screenshot89-            // otherwise-FLAG_SECURE screens (SecureFlagEffect). Off unless asked.9089             QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra(9190                 QaFlags.EXTRA_DISABLE_SECURE_CAPTURE,9291                 false,@@ -111,6 +110,11 @@111110                 }112111             }113112             val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT113+            SideEffect {114+                if (!BuildConfig.DEBUG) {115+                    SecureWindowPolicy.setUserSecure(window, !settings.allowScreenshots)116+                }117+            }114118             PeachTheme(115119                 accent = accent,116120                 reduceMotion = settings.reduceMotion,

view file

app/src/main/java/com/peachpasswords/autofill/AutofillEntryPickerActivity.kt+1 −1
@@ -101,7 +101,7 @@101101         // release-wide FLAG_SECURE set there. Show vault metadata and accept the102102         // master password — protect against screenshots / recents thumbnails.103103         if (!BuildConfig.DEBUG) {104-            SecureWindowPolicy.installRelease(window)104+            SecureWindowPolicy.setUserSecure(window, true)105105         } else if (!qaSecureCaptureBypass()) {106106             // Debug builds are secure by default too. QA must opt out explicitly107107             // on this activity's own launch intent; MainActivity's window policy

view file

app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt+1 −1
@@ -43,7 +43,7 @@4343         // operate on passkey material. Block screenshots / recents in every build4444         // unless an explicit debug QA bypass was placed on this activity's intent.4545         if (!BuildConfig.DEBUG) {46-            SecureWindowPolicy.installRelease(window)46+            SecureWindowPolicy.setUserSecure(window, true)4747         } else if (!qaSecureCaptureBypass()) {4848             SecureWindowPolicy.acquire(window)4949             debugSecureLease = true

view file

app/src/main/java/com/peachpasswords/ui/components/SecureWindowPolicy.kt+17 −15
@@ -16,44 +16,46 @@1616 internal object SecureWindowPolicy {1717     private data class State(1818         var leases: Int = 0,19-        var releaseOwned: Boolean = false,19+        var userSecure: Boolean = true,2020     )2121 2222     private val states = WeakHashMap<Window, State>()2323 24-    /** Install a release-wide owner that is never cleared for this window. */25-    fun installRelease(window: Window) {26-        synchronized(states) {27-            val state = states.getOrPut(window) { State() }28-            state.releaseOwned = true24+    private fun evaluate(window: Window, state: State) {25+        if (state.userSecure || state.leases > 0) {2926             window.setFlags(3027                 WindowManager.LayoutParams.FLAG_SECURE,3128                 WindowManager.LayoutParams.FLAG_SECURE,3229             )30+        } else {31+            window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)3332         }3433     }3534 35+    /** Set the user's screenshot preference for this window. */36+    fun setUserSecure(window: Window, secure: Boolean) {37+        synchronized(states) {38+            val state = states.getOrPut(window) { State() }39+            state.userSecure = secure40+            evaluate(window, state)41+        }42+    }43+3644     /** Acquire one debug/composable lease for a window. */3745     fun acquire(window: Window) {3846         synchronized(states) {3947             val state = states.getOrPut(window) { State() }4048             state.leases++41-            window.setFlags(42-                WindowManager.LayoutParams.FLAG_SECURE,43-                WindowManager.LayoutParams.FLAG_SECURE,44-            )49+            evaluate(window, state)4550         }4651     }4752 48-    /** Release one lease; release-owned windows remain protected forever. */53+    /** Release one lease; re-evaluates the flag. */4954     fun release(window: Window) {5055         synchronized(states) {5156             val state = states[window] ?: return5257             if (state.leases > 0) state.leases--53-            if (state.leases == 0 && !state.releaseOwned) {54-                window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)55-                states.remove(window)56-            }58+            evaluate(window, state)5759         }5860     }5961 }

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+4 −0
@@ -292,6 +292,10 @@292292 293293     SubLabel("LOCKING", fg, top = 0.dp)294294     RowLine(fg) {295+        Name("Allow screenshots", fg)296+        Toggle(s.allowScreenshots, fg) { vm.setAllowScreenshots(it) }297+    }298+    RowLine(fg) {295299         Name("Auto-lock after", fg)296300         val idx = lockOptions.indexOfFirst { it.first == current }.coerceAtLeast(0)297301         SubtleButton(lockOptions[idx].second, fg) {

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+1 −1
@@ -104,7 +104,7 @@104104 private val VERSION_LABEL_FOOTPRINT = PeachSpacing.xs + 12.dp105105 106106 /** Clean gap left between the lowest fanned card and the build label. */107-private val DECK_TO_VERSION_GAP = 6.dp107+private val DECK_TO_VERSION_GAP = 2.dp108108 109109 /** Drag distance past which a stack card flips between focused and deck states. */110110 private val DRAG_THRESHOLD = 60.dp

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsViewModel.kt+1 −0
@@ -281,6 +281,7 @@281281     fun setBreachMonitoring(on: Boolean) = mutate { it.copy(breachMonitoring = on) }282282     fun setBiometrics(on: Boolean) = mutate { it.copy(biometricsEnabled = on) }283283     fun setPasskeyProviderEnabled(on: Boolean) = mutate { it.copy(passkeyProviderEnabled = on) }284+    fun setAllowScreenshots(on: Boolean) = mutate { it.copy(allowScreenshots = on) }284285 285286     /**286287      * Enable biometric unlock: prompt to wrap the live vault key under a hardware

view file

app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+0 −1
@@ -399,7 +399,6 @@399399                 .padding(horizontal = PeachSpacing.md, vertical = PeachSpacing.sm)400400                 .clip(RoundedCornerShape(12.dp))401401                 .background(peachColors.S2)402-                .border(1.dp, peachColors.T3, RoundedCornerShape(12.dp))403402                 .padding(horizontal = PeachSpacing.md, vertical = PeachSpacing.sm)404403                 // C1: inverse of EntryDetailSearchStrip's slide-up-to-open —405404                 // slide the list's own search bar down to collapse back to

view file

app/src/main/java/com/peachpasswords/vault/VaultSettings.kt+1 −0
@@ -14,6 +14,7 @@1414     val passkeyProviderEnabled: Boolean = false,1515     val duressConfigured: Boolean = false,1616     val shortcuts: Map<String, ShortcutBinding> = emptyMap(),17+    val allowScreenshots: Boolean = false,1718 )1819 1920 @Serializable

view file