aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt20
-rw-r--r--app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt6
-rw-r--r--docs/superpowers/plans/2026-11-02-compose-entry.md4
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**