diff options
3 files changed, 21 insertions, 9 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt b/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt index a54b517d..5c16b5a3 100644 --- a/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt +++ b/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember @@ -104,6 +105,9 @@ fun EntryDestination( } } + // ponytail: after process death during key import or the editor, the unlock flow can start + // twice or a prompt can be left over the list; rare, no secret exposed. Fix by tracking + // whether the flow already started if it shows up. val keyImport = rememberLauncherForActivityResult(StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) unlock() else onClose() @@ -173,13 +177,15 @@ fun EntryDestination( ) EntryPrompt.Passphrase, EntryPrompt.WrongPassphrase -> - PassphraseDialog( - wrong = viewModel.prompt == EntryPrompt.WrongPassphrase, - cacheEnabled = viewModel.cacheEnabled, - clearCache = viewModel.clearCache, - onSubmit = { passphrase, clear -> viewModel.submit(passphrase, clear, activity) }, - onCancel = viewModel::cancel, - ) + key(viewModel.retries) { + PassphraseDialog( + wrong = viewModel.prompt == EntryPrompt.WrongPassphrase, + cacheEnabled = viewModel.cacheEnabled, + clearCache = viewModel.clearCache, + onSubmit = { passphrase, clear -> viewModel.submit(passphrase, clear, activity) }, + onCancel = viewModel::cancel, + ) + } } } } diff --git a/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt b/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt index 38d3b9d9..5714f2a6 100644 --- a/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt +++ b/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt @@ -86,7 +86,10 @@ constructor( private var started = false private var copyOnDecrypt = false - private var retries = 0 + /** Failed-or-asked passphrase prompts so far; keys the dialog so each attempt starts fresh. */ + var retries = 0 + private set + private var auth: BiometricResult = BiometricResult.CanceledByUser private var ids: List<PGPIdentifier> = emptyList() private var file: File? = null @@ -115,6 +118,7 @@ constructor( } fun submit(passphrase: String, clearCache: Boolean, context: Context) { + if (prompt == EntryPrompt.None) return prompt = EntryPrompt.None this.clearCache = clearCache val app = context.applicationContext diff --git a/docs/superpowers/plans/2026-11-02-compose-entry.md b/docs/superpowers/plans/2026-11-02-compose-entry.md index 684c7e8b..bd4314ca 100644 --- a/docs/superpowers/plans/2026-11-02-compose-entry.md +++ b/docs/superpowers/plans/2026-11-02-compose-entry.md @@ -56,6 +56,8 @@ Spec: `docs/superpowers/specs/2026-11-02-compose-ui-overhaul-design.md`. Earlier | `DecryptActivity`, `decrypt_layout.xml`, `item_field.xml`, `menu/pgp_handler.xml`, `drawable/divider.xml`, `FieldItemAdapter`, `FieldItem`, upstream's unused `ViewPasswordScreen.kt` | Delete (Task 6) | | `BasePGPActivity`: `repoPath`, `fullPath`, key import, injected `settings`, `repository`, `dispatcherProvider`, `FLAG_SECURE`, path helpers in the companion | Keep for `PasswordCreationActivity` and `AutofillDecryptActivity` | | `BasePGPActivity.name` | Delete (only `DecryptActivity` used it) | +| Text selection of field values (old `textIsSelectable`) | Dropped: the copy buttons set the sensitive clipboard flag, selection toolbar copies would not | +| `TotpModule` | Also installed in `ViewModelComponent` (needed for `PasswordEntry.Factory` in the ViewModel) | Left for later steps because other legacy screens still use them: `PasswordDialog` and `dialog_password_entry.xml` (`AutofillDecryptActivity`, step 7), `EditPasswordScreen.kt`, `PasswordField.kt`/`CopyButton`, `APSAppBar.kt` and the ui:compose `PasswordItem.kt` (unused upstream WIP, step 4 or 8), `BasePGPActivity` (steps 4 and 7). @@ -65,7 +67,7 @@ Left for later steps because other legacy screens still use them: `PasswordDialo - Slack compose-lint: no composable members on the activity (`EntryDestination` and `EntryScreen` are top-level and take the activity as a parameter, like `ListDestination`); every public composable has `modifier: Modifier = Modifier` as its first optional parameter; the only collection parameter is inside `EntryFields` (`ImmutableList`, class marked `@Immutable`); the ViewModel is not passed down. - Accessibility: every `IconButton` has a content description (back, edit, more options, show or hide password, copy); there is no FAB on this screen. `LetterAvatar` clears its own semantics, so the name is read once. - Focus: the entry screen has no text field; the passphrase dialog requests focus inside its own window, so nothing is left focused when the list comes back (the list's own return handling from step 2 then applies). -- Decryption and file IO never run on the main thread. +- Decryption and entry file IO never run on the main thread. **Conventions for every task** |
