aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/lint-baseline.xml22
-rw-r--r--app/lint.xml3
-rw-r--r--app/src/main/AndroidManifest.xml4
-rw-r--r--app/src/main/java/app/passwordstore/data/password/FieldItem.kt51
-rw-r--r--app/src/main/java/app/passwordstore/ui/adapters/FieldItemAdapter.kt101
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt7
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt298
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/ViewPasswordScreen.kt132
-rw-r--r--app/src/main/res/drawable/divider.xml10
-rw-r--r--app/src/main/res/drawable/ic_content_copy.xml15
-rw-r--r--app/src/main/res/layout/decrypt_layout.xml65
-rw-r--r--app/src/main/res/layout/item_field.xml30
-rw-r--r--app/src/main/res/menu/pgp_handler.xml28
13 files changed, 0 insertions, 766 deletions
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index d9be2a91..20c4683f 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -63,28 +63,6 @@
</issue>
<issue
- id="DenyListedApi"
- message="Use Context#getDrawableCompat() instead"
- errorLine1=" ContextCompat.getDrawable(itemView.context, R.drawable.ic_content_copy)"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/app/passwordstore/ui/adapters/FieldItemAdapter.kt"
- line="69"
- column="31"/>
- </issue>
-
- <issue
- id="DenyListedApi"
- message="Use Context#getDrawableCompat() instead"
- errorLine1=" ContextCompat.getDrawable(itemView.context, R.drawable.ic_content_copy)"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/app/passwordstore/ui/adapters/FieldItemAdapter.kt"
- line="69"
- column="31"/>
- </issue>
-
- <issue
id="MissingQuantity"
message="For locale &quot;it&quot; (Italian) the following quantity should also be defined: `many`"
errorLine1=" &lt;plurals name=&quot;delete_title&quot;>"
diff --git a/app/lint.xml b/app/lint.xml
index 6461b16f..c0d501e0 100644
--- a/app/lint.xml
+++ b/app/lint.xml
@@ -31,9 +31,6 @@
<issue id="PrivateResource">
<ignore path="src/main/res/layout/map_preference_widget_switch.xml" />
</issue>
- <issue id="Deprecated">
- <ignore path="src/main/res/layout/item_field.xml" />
- </issue>
<issue id="VectorPath">
<ignore path="src/main/res/drawable/app_settings_alt_24px.xml" />
<ignore path="src/main/res/drawable/ic_launcher_foreground_monochrome.xml" />
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9bfb3b6a..d534d899 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -45,10 +45,6 @@
android:windowSoftInputMode="adjustResize" />
<activity
- android:name=".ui.crypto.DecryptActivity"
- android:exported="false" />
-
- <activity
android:name=".ui.main.LaunchActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
diff --git a/app/src/main/java/app/passwordstore/data/password/FieldItem.kt b/app/src/main/java/app/passwordstore/data/password/FieldItem.kt
deleted file mode 100644
index 56ee8e0e..00000000
--- a/app/src/main/java/app/passwordstore/data/password/FieldItem.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.data.password
-
-import app.passwordstore.data.passfile.Totp
-
-class FieldItem
-private constructor(
- val type: ItemType,
- val label: String,
- val value: String,
- val action: ActionType,
-) {
- enum class ActionType {
- COPY,
- HIDE,
- }
-
- enum class ItemType() {
- USERNAME,
- PASSWORD,
- OTP,
- FREEFORM,
- }
-
- companion object {
- fun createOtpField(label: String, totp: Totp): FieldItem {
- return FieldItem(
- ItemType.OTP,
- label.format(totp.remainingTime.inWholeSeconds),
- totp.value,
- ActionType.COPY,
- )
- }
-
- fun createPasswordField(label: String, password: String): FieldItem {
- return FieldItem(ItemType.PASSWORD, label, password, ActionType.HIDE)
- }
-
- fun createUsernameField(label: String, username: String): FieldItem {
- return FieldItem(ItemType.USERNAME, label, username, ActionType.COPY)
- }
-
- fun createFreeformField(label: String, content: String): FieldItem {
- return FieldItem(ItemType.FREEFORM, label, content, ActionType.COPY)
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/adapters/FieldItemAdapter.kt b/app/src/main/java/app/passwordstore/ui/adapters/FieldItemAdapter.kt
deleted file mode 100644
index 57428ffb..00000000
--- a/app/src/main/java/app/passwordstore/ui/adapters/FieldItemAdapter.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.adapters
-
-import android.text.method.PasswordTransformationMethod
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.content.ContextCompat
-import androidx.core.content.res.ResourcesCompat
-import androidx.recyclerview.widget.RecyclerView
-import app.passwordstore.R
-import app.passwordstore.data.passfile.Totp
-import app.passwordstore.data.password.FieldItem
-import app.passwordstore.databinding.ItemFieldBinding
-import app.passwordstore.ui.compose.R as composeR
-import com.google.android.material.textfield.TextInputLayout
-
-class FieldItemAdapter(
- private var fieldItemList: List<FieldItem>,
- private val showPassword: Boolean,
- private val copyTextToClipboard: (text: String?) -> Unit,
-) : RecyclerView.Adapter<FieldItemAdapter.FieldItemViewHolder>() {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldItemViewHolder {
- val binding = ItemFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return FieldItemViewHolder(binding.root, binding)
- }
-
- override fun onBindViewHolder(holder: FieldItemViewHolder, position: Int) {
- holder.bind(fieldItemList[position], showPassword, copyTextToClipboard)
- }
-
- override fun getItemCount(): Int {
- return fieldItemList.size
- }
-
- fun updateOTPCode(totp: Totp, labelFormat: String) {
- var otpItemPosition = -1
- fieldItemList =
- fieldItemList.mapIndexed { position, item ->
- if (item.type == FieldItem.ItemType.OTP) {
- otpItemPosition = position
- return@mapIndexed FieldItem.createOtpField(labelFormat, totp)
- }
-
- return@mapIndexed item
- }
-
- notifyItemChanged(otpItemPosition)
- }
-
- class FieldItemViewHolder(itemView: View, val binding: ItemFieldBinding) :
- RecyclerView.ViewHolder(itemView) {
-
- fun bind(fieldItem: FieldItem, showPassword: Boolean, copyTextToClipboard: (String?) -> Unit) {
- with(binding) {
- itemText.hint = fieldItem.label
- itemTextContainer.hint = fieldItem.label
- itemText.setText(fieldItem.value)
-
- when (fieldItem.action) {
- FieldItem.ActionType.COPY -> {
- itemTextContainer.apply {
- endIconDrawable =
- ContextCompat.getDrawable(itemView.context, R.drawable.ic_content_copy)
- endIconMode = TextInputLayout.END_ICON_CUSTOM
- setEndIconOnClickListener { copyTextToClipboard(itemText.text.toString()) }
- }
- itemText.transformationMethod = null
- }
- FieldItem.ActionType.HIDE -> {
- itemTextContainer.apply {
- endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
- setOnClickListener { copyTextToClipboard(itemText.text.toString()) }
- }
- itemText.apply {
- transformationMethod =
- if (!showPassword) {
- PasswordTransformationMethod.getInstance()
- } else {
- null
- }
- if (fieldItem.type == FieldItem.ItemType.PASSWORD) {
- typeface =
- ResourcesCompat.getFont(
- binding.root.context,
- composeR.font.jetbrainsmono_nl_regular,
- )
- }
- setOnClickListener { copyTextToClipboard(itemText.text.toString()) }
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt
index 95dc67ad..ba49a62c 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt
@@ -50,13 +50,6 @@ open class BasePGPActivity : AppCompatActivity() {
/** Full path to the password file being worked on */
val fullPath by unsafeLazy { intent.getStringExtra("FILE_PATH")!! }
- /**
- * Name of the password file
- *
- * Converts personal/auth.foo.org/john_doe@example.org.gpg to john_doe.example.org
- */
- val name: String by unsafeLazy { File(fullPath).nameWithoutExtension }
-
/** Action to invoke if [keyImportAction] succeeds. */
private var onKeyImport: (() -> Unit)? = null
private val keyImportAction =
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
deleted file mode 100644
index 8b2109bf..00000000
--- a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.crypto
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import androidx.core.content.edit
-import androidx.fragment.app.setFragmentResultListener
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.Application.Companion.screenWasOff
-import app.passwordstore.R
-import app.passwordstore.crypto.PGPIdentifier
-import app.passwordstore.data.crypto.PGPPassphraseCache
-import app.passwordstore.data.passfile.PasswordEntry
-import app.passwordstore.data.password.FieldItem
-import app.passwordstore.databinding.DecryptLayoutBinding
-import app.passwordstore.ui.adapters.FieldItemAdapter
-import app.passwordstore.util.auth.BiometricAuthenticator
-import app.passwordstore.util.auth.BiometricAuthenticator.Result as BiometricResult
-import app.passwordstore.util.extensions.getString
-import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.features.Feature.EnablePGPPassphraseCache
-import app.passwordstore.util.features.Features
-import app.passwordstore.util.settings.Constants
-import app.passwordstore.util.settings.PreferenceKeys
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.ByteArrayOutputStream
-import java.io.File
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.asLog
-import logcat.logcat
-
-@AndroidEntryPoint
-class DecryptActivity : BasePGPActivity() {
-
- @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
- @Inject lateinit var passphraseCache: PGPPassphraseCache
- @Inject lateinit var features: Features
-
- private val binding by viewBinding(DecryptLayoutBinding::inflate)
- private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
- private var passwordEntry: PasswordEntry? = null
- private var retries = 0
- private var clearCache = true
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- title = name
- with(binding) {
- setContentView(root)
- passwordCategory.text = relativeParentPath
- passwordFile.text = name
- passwordFile.setOnLongClickListener {
- copyTextToClipboard(name)
- true
- }
- }
- if (
- features.isEnabled(EnablePGPPassphraseCache) &&
- BiometricAuthenticator.canAuthenticate(this@DecryptActivity)
- ) {
- BiometricAuthenticator.authenticate(
- this@DecryptActivity,
- R.string.biometric_prompt_title_gpg_passphrase_cache,
- ) { authResult ->
- requireKeysExist { decrypt(isError = false, authResult) }
- }
- } else {
- requireKeysExist { decrypt(isError = false, BiometricResult.CanceledByUser) }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.pgp_handler, menu)
- passwordEntry?.let { entry ->
- menu.findItem(R.id.edit_password).isVisible = true
- if (!entry.password.isNullOrBlank()) {
- menu.findItem(R.id.share_password_as_plaintext).isVisible = true
- menu.findItem(R.id.copy_password).isVisible = true
- }
- }
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> onBackPressedDispatcher.onBackPressed()
- R.id.edit_password -> editPassword()
- R.id.share_password_as_plaintext -> shareAsPlaintext()
- R.id.copy_password -> copyPasswordToClipboard(passwordEntry?.password)
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- /**
- * Automatically finishes the activity after [PreferenceKeys.GENERAL_SHOW_TIME] seconds decryption
- * succeeded to prevent information leaks from stale activities.
- */
- private fun startAutoDismissTimer() {
- lifecycleScope.launch {
- val timeout =
- settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull()
- ?: Constants.DEFAULT_DECRYPTION_TIMEOUT
- if (timeout != 0) {
- delay(timeout.seconds)
- finish()
- }
- }
- }
-
- /**
- * Edit the current password and hide all the fields populated by encrypted data so that when the
- * result triggers they can be repopulated with new data.
- */
- private fun editPassword() {
- val intent = Intent(this, PasswordCreationActivity::class.java)
- intent.action = Intent.ACTION_VIEW
- intent.putExtra("FILE_PATH", relativeParentPath)
- intent.putExtra("REPO_PATH", repoPath)
- intent.putExtra(PasswordCreationActivity.EXTRA_FILE_NAME, name)
- intent.putExtra(PasswordCreationActivity.EXTRA_USERNAME, passwordEntry?.username)
- intent.putExtra(PasswordCreationActivity.EXTRA_PASSWORD, passwordEntry?.password)
- intent.putExtra(
- PasswordCreationActivity.EXTRA_EXTRA_CONTENT,
- passwordEntry?.extraContentWithoutUsername,
- )
- intent.putExtra(PasswordCreationActivity.EXTRA_EDITING, true)
- startActivity(intent)
- finish()
- }
-
- private fun shareAsPlaintext() {
- val sendIntent =
- Intent().apply {
- action = Intent.ACTION_SEND
- putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
- type = "text/plain"
- }
- // Always show a picker to give the user a chance to cancel
- startActivity(
- Intent.createChooser(sendIntent, resources.getText(R.string.send_plaintext_password_to))
- )
- }
-
- private fun decrypt(isError: Boolean, authResult: BiometricResult) {
- val gpgIdentifiers = getPGPIdentifiers(relativeParentPath) ?: return
- lifecycleScope.launch(dispatcherProvider.main()) {
- when (authResult) {
- // Internally handled by the prompt dialog
- is BiometricResult.Retry -> {}
- // If the dialog is dismissed for any reason, prompt for passphrase
- is BiometricResult.CanceledByUser,
- is BiometricResult.CanceledBySystem,
- is BiometricResult.Failure,
- is BiometricResult.HardwareUnavailableOrDisabled ->
- askPassphrase(isError, gpgIdentifiers, authResult)
- is BiometricResult.Success -> {
- /* clear passphrase cache on first use after application startup or if screen was off;
- also make sure to purge a stale cache after caching has been disabled via PGP settings */
- clearCache = settings.getBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, true)
- if (screenWasOff && clearCache) {
- passphraseCache.clearAllCachedPassphrases(this@DecryptActivity)
- screenWasOff = false
- }
- val cachedPassphrase =
- passphraseCache.retrieveCachedPassphrase(this@DecryptActivity, gpgIdentifiers.first())
- if (cachedPassphrase != null) {
- decryptWithPassphrase(cachedPassphrase, gpgIdentifiers, authResult)
- } else {
- askPassphrase(isError, gpgIdentifiers, authResult)
- }
- }
- }
- }
- }
-
- private suspend fun askPassphrase(
- isError: Boolean,
- gpgIdentifiers: List<PGPIdentifier>,
- authResult: BiometricResult,
- ) {
- if (retries < MAX_RETRIES) {
- retries += 1
- } else {
- finish()
- }
- if (!repository.isPasswordProtected(gpgIdentifiers)) {
- decryptWithPassphrase(passphrase = "", gpgIdentifiers, authResult)
- return
- }
- val dialog =
- PasswordDialog.newInstance(
- cacheEnabled = features.isEnabled(EnablePGPPassphraseCache),
- clearCache = clearCache,
- )
- if (isError) {
- dialog.setError()
- }
- dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
- dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle ->
- if (key == PasswordDialog.PASSWORD_RESULT_KEY) {
- val passphrase = bundle.getString(PasswordDialog.PASSWORD_PHRASE_KEY)!!
- clearCache = bundle.getBoolean(PasswordDialog.PASSWORD_CLEAR_KEY)
- lifecycleScope.launch(dispatcherProvider.main()) {
- decryptWithPassphrase(passphrase, gpgIdentifiers, authResult) {
- runCatching {
- if (authResult is BiometricResult.Success) {
- passphraseCache.cachePassphrase(
- this@DecryptActivity,
- gpgIdentifiers.first(),
- passphrase,
- )
- settings.edit { putBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, clearCache) }
- }
- }
- .onFailure { e -> logcat { e.asLog() } }
- }
- }
- }
- }
- }
-
- private suspend fun decryptWithPassphrase(
- passphrase: String,
- identifiers: List<PGPIdentifier>,
- authResult: BiometricResult,
- onSuccess: suspend () -> Unit = {},
- ) {
- val message = withContext(dispatcherProvider.io()) { File(fullPath).readBytes().inputStream() }
- val outputStream = ByteArrayOutputStream()
- val result = repository.decrypt(passphrase, identifiers, message, outputStream)
- if (result.isOk) {
- val entry = passwordEntryFactory.create(result.value.toByteArray())
- passwordEntry = entry
- createPasswordUI(entry)
- startAutoDismissTimer()
- onSuccess()
- } else {
- logcat(ERROR) { result.error.stackTraceToString() }
- decrypt(isError = true, authResult = authResult)
- }
- }
-
- private suspend fun createPasswordUI(entry: PasswordEntry) =
- withContext(dispatcherProvider.main()) {
- val labelFormat = resources.getString(R.string.otp_label_format)
- val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true)
- invalidateOptionsMenu()
-
- val items = arrayListOf<FieldItem>()
- if (!entry.password.isNullOrBlank()) {
- items.add(FieldItem.createPasswordField(getString(R.string.password), entry.password!!))
- if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) {
- copyPasswordToClipboard(entry.password)
- }
- }
-
- if (entry.hasTotp()) {
- items.add(FieldItem.createOtpField(labelFormat, entry.totp.first()))
- }
-
- if (!entry.username.isNullOrBlank()) {
- items.add(FieldItem.createUsernameField(getString(R.string.username), entry.username!!))
- }
-
- entry.extraContent.forEach { (key, value) ->
- items.add(FieldItem.createFreeformField(key, value))
- }
-
- val adapter = FieldItemAdapter(items, showPassword) { text -> copyTextToClipboard(text) }
- binding.recyclerView.adapter = adapter
- binding.recyclerView.itemAnimator = null
-
- if (entry.hasTotp()) {
- lifecycleScope.launch { entry.totp.collect { adapter.updateOTPCode(it, labelFormat) } }
- }
- }
-
- private companion object {
- private const val MAX_RETRIES = 3
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/ViewPasswordScreen.kt b/app/src/main/java/app/passwordstore/ui/crypto/ViewPasswordScreen.kt
deleted file mode 100644
index f2a67706..00000000
--- a/app/src/main/java/app/passwordstore/ui/crypto/ViewPasswordScreen.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.crypto
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.capitalize
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.tooling.preview.Preview
-import app.passwordstore.R
-import app.passwordstore.data.passfile.PasswordEntry
-import app.passwordstore.ui.APSAppBar
-import app.passwordstore.ui.compose.CopyButton
-import app.passwordstore.ui.compose.PasswordField
-import app.passwordstore.ui.compose.theme.APSTheme
-import app.passwordstore.ui.compose.theme.SpacingLarge
-import app.passwordstore.ui.compose.theme.SpacingMedium
-import app.passwordstore.util.time.UserClock
-import app.passwordstore.util.totp.UriTotpFinder
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-
-/** Composable to show a decrypted [PasswordEntry]. */
-@Composable
-fun ViewPasswordScreen(
- entryName: String,
- entry: PasswordEntry,
- onNavigateUp: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- Scaffold(
- topBar = {
- APSAppBar(
- title = entryName,
- navigationIcon = painterResource(R.drawable.ic_arrow_back_black_24dp),
- onNavigationIconClick = onNavigateUp,
- backgroundColor = MaterialTheme.colorScheme.surface,
- )
- }
- ) { paddingValues ->
- Box(modifier = modifier.padding(paddingValues)) {
- Column(
- modifier =
- Modifier.padding(vertical = SpacingMedium, horizontal = SpacingLarge).fillMaxSize()
- ) {
- if (entry.password != null) {
- PasswordField(
- value = entry.password!!,
- label = stringResource(R.string.password),
- initialVisibility = false,
- readOnly = true,
- modifier = Modifier.padding(bottom = SpacingMedium).fillMaxWidth(),
- )
- }
- if (entry.hasTotp()) {
- val totp by entry.totp.collectAsState(runBlocking { entry.totp.first() })
- TextField(
- value = totp.value,
- onValueChange = {},
- readOnly = true,
- label = { Text("OTP (expires in ${totp.remainingTime.inWholeSeconds}s)") },
- trailingIcon = { CopyButton(totp.value, R.string.copy_label) },
- modifier = Modifier.padding(bottom = SpacingMedium).fillMaxWidth(),
- )
- }
- if (entry.username != null) {
- TextField(
- value = entry.username!!,
- onValueChange = {},
- readOnly = true,
- label = { Text(stringResource(R.string.username)) },
- trailingIcon = { CopyButton(entry.username!!, R.string.copy_label) },
- modifier = Modifier.padding(bottom = SpacingMedium).fillMaxWidth(),
- )
- }
- ExtraContent(entry = entry)
- }
- }
- }
-}
-
-@Composable
-private fun ExtraContent(entry: PasswordEntry, modifier: Modifier = Modifier) {
- entry.extraContent.forEach { (label, value) ->
- TextField(
- value = value,
- onValueChange = {},
- readOnly = true,
- label = { Text(label.capitalize(Locale.current)) },
- trailingIcon = { CopyButton(value, R.string.copy_label) },
- modifier = modifier.padding(bottom = SpacingMedium).fillMaxWidth(),
- )
- }
-}
-
-@Preview
-@Composable
-private fun ViewPasswordScreenPreview() {
- APSTheme {
- ViewPasswordScreen(entryName = "Test Entry", entry = createTestEntry(), onNavigateUp = {})
- }
-}
-
-private fun createTestEntry() =
- PasswordEntry(
- UserClock(),
- UriTotpFinder(),
- """
- |My Password
- |otpauth://totp/ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
- |login: msfjarvis
- |URL: example.com
- """
- .trimMargin()
- .encodeToByteArray(),
- )
diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml
deleted file mode 100644
index 39ec0614..00000000
--- a/app/src/main/res/drawable/divider.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <size android:height="1dp" />
- <solid android:color="?attr/colorPrimaryDark" />
-</shape>
diff --git a/app/src/main/res/drawable/ic_content_copy.xml b/app/src/main/res/drawable/ic_content_copy.xml
deleted file mode 100644
index cdee1758..00000000
--- a/app/src/main/res/drawable/ic_content_copy.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:tint="?attr/colorControlNormal"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="#FFFFFF"
- android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM15,5L8,5c-1.1,0 -1.99,0.9 -1.99,2L6,21c0,1.1 0.89,2 1.99,2L19,23c1.1,0 2,-0.9 2,-2L21,11l-6,-6zM8,21L8,7h6v5h5v9L8,21z" />
-</vector>
diff --git a/app/src/main/res/layout/decrypt_layout.xml b/app/src/main/res/layout/decrypt_layout.xml
deleted file mode 100644
index 88c0fb1a..00000000
--- a/app/src/main/res/layout/decrypt_layout.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingTop="16dp"
- tools:context="app.passwordstore.ui.crypto.DecryptActivity">
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@+id/password_category"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="32dp"
- android:layout_marginEnd="16dp"
- android:textIsSelectable="false"
- android:textSize="18sp"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="CATEGORY HERE" />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@+id/password_file"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginStart="32dp"
- android:layout_marginEnd="16dp"
- android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
- android:textColor="?attr/colorSecondary"
- android:textSize="24sp"
- android:textStyle="bold"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/password_category"
- tools:text="PASSWORD FILE NAME HERE" />
-
- <androidx.appcompat.widget.AppCompatImageView
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="16dp"
- android:src="@drawable/divider"
- app:layout_constraintTop_toBottomOf="@id/password_file"
- tools:ignore="ContentDescription" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/recycler_view"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:paddingHorizontal="16dp"
- app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/divider"
- tools:listitem="@layout/item_field" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/item_field.xml b/app/src/main/res/layout/item_field.xml
deleted file mode 100644
index 79697a56..00000000
--- a/app/src/main/res/layout/item_field.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <com.google.android.material.textfield.TextInputLayout
- android:id="@+id/item_text_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- tools:hint="@string/password"
- tools:visibility="visible">
-
- <com.google.android.material.textfield.TextInputEditText
- android:id="@+id/item_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:editable="false"
- android:textIsSelectable="true"
- tools:text="p@55w0rd!" />
-
- </com.google.android.material.textfield.TextInputLayout>
-
-</LinearLayout>
diff --git a/app/src/main/res/menu/pgp_handler.xml b/app/src/main/res/menu/pgp_handler.xml
deleted file mode 100644
index a8b6a524..00000000
--- a/app/src/main/res/menu/pgp_handler.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:pwstore="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- tools:context=".ui.crypto.PgpActivity">
- <item
- android:id="@+id/share_password_as_plaintext"
- android:icon="@drawable/ic_share_24dp"
- android:title="@string/share_as_plaintext"
- android:visible="false"
- pwstore:showAsAction="ifRoom" />
- <item
- android:id="@+id/copy_password"
- android:icon="@drawable/ic_content_copy"
- android:title="@string/copy_password"
- android:visible="false"
- pwstore:showAsAction="ifRoom" />
- <item
- android:id="@+id/edit_password"
- android:icon="@drawable/ic_edit_24dp"
- android:title="@string/edit_password"
- android:visible="false"
- pwstore:showAsAction="ifRoom" />
-</menu>