aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/app/passwordstore/ui/editor/EditorDestination.kt262
-rw-r--r--app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt41
-rw-r--r--app/src/main/java/app/passwordstore/ui/list/ListDestination.kt5
-rw-r--r--app/src/main/java/app/passwordstore/ui/main/MainActivity.kt106
4 files changed, 359 insertions, 55 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/editor/EditorDestination.kt b/app/src/main/java/app/passwordstore/ui/editor/EditorDestination.kt
new file mode 100644
index 00000000..8b66ba5e
--- /dev/null
+++ b/app/src/main/java/app/passwordstore/ui/editor/EditorDestination.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package app.passwordstore.ui.editor
+
+import android.app.Activity
+import android.content.pm.PackageManager
+import androidx.activity.compose.BackHandler
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts.GetContent
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.res.stringResource
+import androidx.fragment.app.FragmentActivity
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.lifecycleScope
+import app.passwordstore.R
+import app.passwordstore.data.repo.PasswordRepository
+import app.passwordstore.ui.crypto.copyPasswordToClipboard
+import app.passwordstore.ui.crypto.getPGPIdentifiers
+import app.passwordstore.ui.list.FolderPicker
+import app.passwordstore.util.extensions.commitChange
+import app.passwordstore.util.extensions.getString
+import app.passwordstore.util.extensions.isInsideRepository
+import app.passwordstore.util.extensions.sharedPrefs
+import app.passwordstore.util.extensions.snackbar
+import app.passwordstore.util.settings.PreferenceKeys
+import com.google.android.material.snackbar.Snackbar
+import com.google.zxing.integration.android.IntentIntegrator
+import java.io.File
+import kotlinx.coroutines.launch
+
+/**
+ * The editor: wires [EditorScreen] to the [viewModel], the generator sheet, the folder picker, OTP
+ * import and the save path (checks, recipients from `.gpg-id`, encryption, write, git commit).
+ * [initial] fills the form the first time; null means the entry to edit is no longer in memory
+ * (process death) and the editor closes. [original] is the file being edited, null for a new entry.
+ * [generate] opens the generator at once (autofill's "generate password"). [onClose] leaves without
+ * saving; [onSaved] runs after the commit.
+ */
+@Composable
+fun EditorDestination(
+ activity: FragmentActivity,
+ initial: EditorForm?,
+ original: File?,
+ onClose: () -> Unit,
+ onSaved: (SaveResult.Saved) -> Unit,
+ modifier: Modifier = Modifier,
+ generate: Boolean = false,
+ viewModel: EditorViewModel = hiltViewModel(),
+) {
+ val root = PasswordRepository.getRepositoryDirectory()
+ // ponytail: filled once per ViewModel, so a recreated activity keeps the edits; an edit restored
+ // after process death has nothing to fill from (the entry view's plaintext is gone) and closes,
+ // back to the entry view's passphrase prompt.
+ val first = remember { initial != null && viewModel.start(initial) }
+ val lost = !viewModel.started
+ LaunchedEffect(lost) { if (lost) onClose() }
+ if (lost) return
+
+ val keyboard = LocalSoftwareKeyboardController.current
+ val focusManager = LocalFocusManager.current
+ val scope = rememberCoroutineScope()
+ var sheet by rememberSaveable { mutableStateOf(first && generate) }
+ var picker by rememberSaveable { mutableStateOf(false) }
+ var otp by rememberSaveable { mutableStateOf(false) }
+ var failure by remember { mutableStateOf<String?>(null) }
+ val form = viewModel.form
+ val hasOtp = remember(form.extra) { viewModel.hasOtp(form.extra) }
+ val hasCamera = remember {
+ activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
+ }
+ val showPassword = remember {
+ activity.sharedPrefs.getBoolean(PreferenceKeys.SHOW_PASSWORD, true)
+ }
+ val classicFirst = remember {
+ activity.sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) == "classic"
+ }
+
+ fun say(message: Int) {
+ activity.snackbar(message = activity.getString(message))
+ }
+
+ fun addLine(line: String) {
+ viewModel.form = viewModel.form.copy(extra = withLine(viewModel.form.extra, line))
+ }
+
+ // The one way out without saving: only while no save runs, keyboard and focus dropped first.
+ fun close() {
+ if (viewModel.saving) return
+ keyboard?.hide()
+ focusManager.clearFocus()
+ onClose()
+ }
+
+ fun save(copy: Boolean) {
+ if (viewModel.saving) return
+ val current = viewModel.form
+ val target = entryFile(root, current)
+ val problem =
+ formError(current)
+ ?: R.string.message_error_destination_outside_repo.takeUnless {
+ target.isInsideRepository()
+ }
+ if (problem != null) return say(problem)
+ // Recipients from the nearest .gpg-id, as pass does; null: an invalid ID, already reported.
+ val ids = activity.getPGPIdentifiers(current.dir) ?: return
+ activity.lifecycleScope.launch {
+ when (val result = viewModel.save(ids, target, original, activity)) {
+ null -> Unit
+ is SaveResult.Failed -> failure = activity.getString(result.message, result.arg)
+ is SaveResult.Saved -> {
+ if (copy) activity.copyPasswordToClipboard(result.form.password)
+ result.staleOriginal?.let {
+ activity.snackbar(
+ message =
+ activity.getString(
+ R.string.password_creation_file_delete_fail_message,
+ it.nameWithoutExtension,
+ ),
+ length = Snackbar.LENGTH_LONG,
+ )
+ }
+ // ponytail: commit failures are not shown, as for the list's move and delete; a theme
+ // or locale change in the middle of a save cancels before this commit, and the next
+ // commit picks the file up.
+ activity.commitChange(
+ activity.getString(
+ if (original != null) R.string.git_commit_edit_text else R.string.git_commit_add_text,
+ target.relativeTo(root).path.removeSuffix(".gpg"),
+ )
+ )
+ keyboard?.hide()
+ focusManager.clearFocus()
+ onSaved(result)
+ }
+ }
+ }
+ }
+
+ BackHandler(onBack = ::close)
+
+ val scan =
+ rememberLauncherForActivityResult(StartActivityForResult()) { result ->
+ val text = IntentIntegrator.parseActivityResult(result.resultCode, result.data)?.contents
+ if (result.resultCode == Activity.RESULT_OK && text != null) {
+ addLine("$text\n")
+ say(R.string.otp_import_success)
+ } else {
+ say(R.string.otp_import_failure_generic)
+ }
+ }
+ val image =
+ rememberLauncherForActivityResult(GetContent()) { uri ->
+ if (uri == null) {
+ say(R.string.otp_import_failure_no_selection)
+ } else {
+ scope.launch {
+ val text = viewModel.readQr(uri, activity)
+ if (text != null) {
+ addLine(text)
+ say(R.string.otp_import_success)
+ } else {
+ say(R.string.otp_import_failure_generic)
+ }
+ }
+ }
+ }
+
+ Box(modifier) {
+ EditorScreen(
+ form = form,
+ editing = original != null,
+ saving = viewModel.saving,
+ showOtpActions = !hasOtp,
+ initiallyRevealed = showPassword,
+ focus =
+ when {
+ !first -> EditorFocus.None
+ form.name.isEmpty() -> EditorFocus.Name
+ original == null -> EditorFocus.Username
+ else -> EditorFocus.None
+ },
+ onFormChange = { viewModel.form = it },
+ onClose = ::close,
+ onSave = ::save,
+ onChangeFolder = { picker = true },
+ onGenerate = { sheet = true },
+ onScanQr =
+ if (hasCamera) {
+ {
+ scan.launch(
+ IntentIntegrator(activity)
+ .setOrientationLocked(false)
+ .setBeepEnabled(false)
+ .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
+ .createScanIntent()
+ )
+ }
+ } else null,
+ onQrImage = { image.launch("image/*") },
+ onManualOtp = { otp = true },
+ )
+ if (sheet)
+ GeneratorSheet(
+ classicFirst = classicFirst,
+ random = viewModel.randomSpec(activity),
+ diceware = viewModel.dicewareSpec(),
+ onRandom = { spec -> viewModel.generateRandom(spec, activity) },
+ onDiceware = viewModel::generateDiceware,
+ onUse = { password ->
+ viewModel.form = viewModel.form.copy(password = password)
+ sheet = false
+ },
+ onDismiss = { sheet = false },
+ )
+ if (picker)
+ FolderPicker(
+ root = root,
+ start = form.dir,
+ onPick = { dir ->
+ viewModel.form = viewModel.form.copy(dir = dir)
+ picker = false
+ },
+ onDismiss = { picker = false },
+ )
+ if (otp)
+ OtpDialog(
+ onAdd = { secret, account ->
+ totpUri(secret, account)?.let(::addLine)
+ otp = false
+ },
+ onDismiss = { otp = false },
+ )
+ failure?.let { message ->
+ AlertDialog(
+ onDismissRequest = { failure = null },
+ title = { Text(stringResource(R.string.password_creation_file_fail_title)) },
+ text = { Text(message) },
+ confirmButton = {
+ TextButton(onClick = { failure = null }) { Text(stringResource(R.string.dialog_ok)) }
+ },
+ )
+ }
+ }
+}
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 5c16b5a3..1b3a75d2 100644
--- a/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt
+++ b/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt
@@ -7,7 +7,6 @@ package app.passwordstore.ui.entry
import android.app.Activity
import android.content.Intent
-import android.view.WindowManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.compose.foundation.layout.Box
@@ -22,7 +21,6 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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
@@ -43,12 +41,9 @@ import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.SecureFlagPolicy
import androidx.hilt.navigation.compose.hiltViewModel
import app.passwordstore.R
-import app.passwordstore.data.passfile.PasswordEntry
import app.passwordstore.data.passfile.Totp
import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.crypto.BasePGPActivity
import app.passwordstore.ui.crypto.BasePGPActivity.Companion.getParentPath
-import app.passwordstore.ui.crypto.PasswordCreationActivity
import app.passwordstore.ui.crypto.copyPasswordToClipboard
import app.passwordstore.ui.crypto.copyTextToClipboard
import app.passwordstore.ui.crypto.getPGPIdentifiers
@@ -62,29 +57,21 @@ import app.passwordstore.util.settings.PreferenceKeys
import java.io.File
/**
- * One entry: wires [EntryScreen] to the [viewModel], the biometric and passphrase prompts, the
- * clipboard and the legacy editor. [onClose] leaves the destination; [onSaved] runs after the
- * editor saved the entry (the host returns to the list).
+ * One entry: wires [EntryScreen] to the [viewModel], the biometric and passphrase prompts and the
+ * clipboard. [onClose] leaves the destination; [onEdit] opens the editor on the decrypted entry.
+ * The host keeps FLAG_SECURE on while this is composed.
*/
@Composable
fun EntryDestination(
activity: MainActivity,
file: File,
onClose: () -> Unit,
- onSaved: () -> Unit,
+ onEdit: () -> Unit,
modifier: Modifier = Modifier,
viewModel: EntryViewModel = hiltViewModel(),
) {
val root = PasswordRepository.getRepositoryDirectory()
- // Secrets stay out of screenshots and recents while this destination is composed.
- // ponytail: plain add/clear, fine while no other secret-showing destination can overlap this
- // one in a transition; count in MainActivity once the editor (step 4) becomes a destination.
- DisposableEffect(Unit) {
- activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
- onDispose { activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) }
- }
-
// Routes come from the app's own listing or from LaunchActivity's check; check once more.
val inRepo = remember(file) { LaunchActivity.isFileInRepo(file.path, root) }
LaunchedEffect(inRepo) { if (!inRepo) onClose() }
@@ -105,17 +92,13 @@ fun EntryDestination(
}
}
- // ponytail: after process death during key import or the editor, the unlock flow can start
+ // ponytail: after process death during key import, 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()
}
- val editor =
- rememberLauncherForActivityResult(StartActivityForResult()) { result ->
- if (result.resultCode == Activity.RESULT_OK) onSaved()
- }
// ponytail: a theme or locale change while the biometric prompt is up loses that prompt's
// callback (the flow already counts as started); back out and reopen the entry.
@@ -150,7 +133,7 @@ fun EntryDestination(
otpFractionLeft = totp?.fractionLeft() ?: 0f,
showPassword = showPassword,
onBack = onClose,
- onEdit = { entry?.let { editor.launch(editIntent(activity, file, root, it)) } },
+ onEdit = { if (entry != null) onEdit() },
onShare = { fields?.password?.let { activity.startActivity(shareIntent(activity, it)) } },
onCopyName = { activity.copyTextToClipboard(file.nameWithoutExtension) },
onCopySecret = { activity.copyPasswordToClipboard(it) },
@@ -252,18 +235,6 @@ private fun PassphraseDialog(
)
}
-/** The legacy editor, filled exactly as the old decrypt screen filled it. */
-private fun editIntent(activity: Activity, file: File, root: File, entry: PasswordEntry): Intent =
- Intent(activity, PasswordCreationActivity::class.java)
- .setAction(Intent.ACTION_VIEW)
- .putExtra(BasePGPActivity.EXTRA_FILE_PATH, getParentPath(file.absolutePath, root.absolutePath))
- .putExtra(BasePGPActivity.EXTRA_REPO_PATH, root.absolutePath)
- .putExtra(PasswordCreationActivity.EXTRA_FILE_NAME, file.nameWithoutExtension)
- .putExtra(PasswordCreationActivity.EXTRA_USERNAME, entry.username)
- .putExtra(PasswordCreationActivity.EXTRA_PASSWORD, entry.password)
- .putExtra(PasswordCreationActivity.EXTRA_EXTRA_CONTENT, entry.extraContentWithoutUsername)
- .putExtra(PasswordCreationActivity.EXTRA_EDITING, true)
-
/** Always a chooser, so the user can still cancel. */
private fun shareIntent(activity: Activity, password: String): Intent =
Intent.createChooser(
diff --git a/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt b/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt
index 5c248dcb..eff3cf02 100644
--- a/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt
+++ b/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt
@@ -60,7 +60,7 @@ private enum class ListDialog {
/**
* The list at [path] (relative to the store root): wires [ListScreen] to the [viewModel], the
* folder picker, the legacy activities and the store operations. [onOpenFolder] takes a path
- * relative to the root, [onOpenEntry] an entry file.
+ * relative to the root, [onOpenEntry] an entry file; [onNewEntry] opens the editor in this folder.
*/
@Composable
fun ListDestination(
@@ -68,6 +68,7 @@ fun ListDestination(
path: String,
onOpenFolder: (relativePath: String) -> Unit,
onOpenEntry: (File) -> Unit,
+ onNewEntry: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SearchableRepositoryViewModel = hiltViewModel(),
) {
@@ -178,7 +179,7 @@ fun ListDestination(
onDismiss = { dialog = ListDialog.None },
onNewEntry = {
dialog = ListDialog.None
- ifInitialized { activity.createEntry(dir) }
+ ifInitialized(onNewEntry)
},
onNewFolder = {
dialog = ListDialog.None
diff --git a/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt b/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt
index 174f7665..1a1ff500 100644
--- a/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt
@@ -5,9 +5,13 @@
package app.passwordstore.ui.main
-import android.content.Intent
import android.os.Bundle
+import android.view.WindowManager
import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@@ -16,8 +20,11 @@ import app.passwordstore.data.password.PasswordItem
import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.ui.compose.theme.APSTheme
import app.passwordstore.ui.crypto.BasePGPActivity
-import app.passwordstore.ui.crypto.PasswordCreationActivity
+import app.passwordstore.ui.editor.EditorDestination
+import app.passwordstore.ui.editor.EditorForm
+import app.passwordstore.ui.editor.editorForm
import app.passwordstore.ui.entry.EntryDestination
+import app.passwordstore.ui.entry.EntryViewModel
import app.passwordstore.ui.git.base.BaseGitActivity
import app.passwordstore.ui.list.ListDestination
import app.passwordstore.ui.list.folderMoves
@@ -49,6 +56,13 @@ import kotlinx.serialization.Serializable
@Serializable data class EntryRoute(val filePath: String)
/**
+ * The editor: a new entry in [dir] (relative to the store root, "" is the root), or the entry
+ * [name] (file name without `.gpg`) there. An edit takes its plaintext from the entry view below it
+ * on the back stack, never from the route.
+ */
+@Serializable data class EditRoute(val dir: String, val name: String? = null)
+
+/**
* Single-activity host for the Compose UI. Not exported: it is only started by [LaunchActivity],
* after its biometric gate and path check. Extends [BaseGitActivity] (an AppCompatActivity, so also
* the FragmentActivity BiometricPrompt needs) because the git operations require one; it hosts no
@@ -59,6 +73,9 @@ class MainActivity : BaseGitActivity() {
@Inject lateinit var shortcutHandler: ShortcutHandler
+ /** Composed destinations that show secrets; FLAG_SECURE is on while this is above zero. */
+ private var secureHolds = 0
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val filePath = intent.getStringExtra(BasePGPActivity.EXTRA_FILE_PATH)
@@ -86,24 +103,66 @@ class MainActivity : BaseGitActivity() {
nav.navigate(EntryRoute(file.absolutePath))
}
},
+ onNewEntry = {
+ if (nav.currentBackStackEntry?.id == entry.id) nav.navigate(EditRoute(path))
+ },
)
}
composable<EntryRoute> { entry ->
+ val file = File(entry.toRoute<EntryRoute>().filePath)
+ SecureWindow(this@MainActivity)
EntryDestination(
activity = this@MainActivity,
- file = File(entry.toRoute<EntryRoute>().filePath),
+ file = file,
onClose = {
// Only the entry on top closes: the auto-close timer can race a back press.
if (nav.currentBackStackEntry?.id == entry.id) {
if (nav.previousBackStackEntry == null) finish() else nav.popBackStack()
}
},
+ onEdit = {
+ if (nav.currentBackStackEntry?.id == entry.id) {
+ val root = PasswordRepository.getRepositoryDirectory().canonicalFile
+ val dir = file.canonicalFile.parentFile?.relativeTo(root)?.path.orEmpty()
+ nav.navigate(EditRoute(dir, file.nameWithoutExtension))
+ }
+ },
+ )
+ }
+ composable<EditRoute> { entry ->
+ val route = entry.toRoute<EditRoute>()
+ val name = route.name
+ // An edit reads the decrypted entry from the entry view below it. After process
+ // death that ViewModel is new and empty, and the editor closes.
+ val decrypted =
+ if (name == null) null
+ else
+ remember(entry) {
+ // No entry view below (none on the stack): getBackStackEntry throws.
+ try {
+ nav.getBackStackEntry<EntryRoute>()
+ } catch (_: IllegalArgumentException) {
+ null
+ }
+ }
+ ?.let { hiltViewModel<EntryViewModel>(it).entry }
+ SecureWindow(this@MainActivity)
+ EditorDestination(
+ activity = this@MainActivity,
+ initial =
+ if (name == null) EditorForm(dir = route.dir)
+ else decrypted?.let { editorForm(it, route.dir, name) },
+ original =
+ name?.let {
+ File(File(PasswordRepository.getRepositoryDirectory(), route.dir), "$it.gpg")
+ },
+ onClose = { if (nav.currentBackStackEntry?.id == entry.id) nav.popBackStack() },
onSaved = {
- // Back to the list, which re-reads the store on resume. Opened from a shortcut,
- // the entry has no list below it: the list root replaces it.
+ // Back to the list, which re-reads the store on resume. An entry opened from a
+ // shortcut has no list below it: the list root replaces the stack.
if (nav.currentBackStackEntry?.id == entry.id) {
- if (nav.previousBackStackEntry != null) nav.popBackStack()
- else nav.navigate(ListRoute()) { popUpTo<EntryRoute> { inclusive = true } }
+ if (!nav.popBackStack<ListRoute>(inclusive = false))
+ nav.navigate(ListRoute()) { popUpTo<EntryRoute> { inclusive = true } }
}
},
)
@@ -113,6 +172,16 @@ class MainActivity : BaseGitActivity() {
}
}
+ /** Counts a destination that shows secrets; the first one turns FLAG_SECURE on. */
+ fun holdSecure() {
+ if (secureHolds++ == 0) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ }
+
+ /** Ends a [holdSecure]; FLAG_SECURE goes off when no secret-showing destination is left. */
+ fun releaseSecure() {
+ if (--secureHolds == 0) window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ }
+
/** Re-reads the store; starts onboarding and returns false when there is none yet. */
fun checkStore(): Boolean {
PasswordRepository.initialize()
@@ -145,17 +214,6 @@ class MainActivity : BaseGitActivity() {
shortcutHandler.addPinnedShortcut(item, item.createAuthEnabledIntent(this))
}
- fun createEntry(dir: File) {
- startActivity(
- Intent(this, PasswordCreationActivity::class.java)
- .putExtra(BasePGPActivity.EXTRA_FILE_PATH, dir.absolutePath)
- .putExtra(
- BasePGPActivity.EXTRA_REPO_PATH,
- PasswordRepository.getRepositoryDirectory().absolutePath,
- )
- )
- }
-
fun openSettings() {
launchActivity(SettingsActivity::class.java)
}
@@ -163,3 +221,15 @@ class MainActivity : BaseGitActivity() {
private fun entryItem(file: File) =
PasswordItem.newPassword(file.name, file, PasswordRepository.getRepositoryDirectory())
}
+
+/**
+ * FLAG_SECURE while composed. Counted in [MainActivity], because in a transition (entry view to
+ * editor and back) the entering destination is composed before the leaving one is disposed.
+ */
+@Composable
+private fun SecureWindow(activity: MainActivity) {
+ DisposableEffect(activity) {
+ activity.holdSecure()
+ onDispose { activity.releaseSecure() }
+ }
+}