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/dialogs/TextInputDialog.kt71
-rw-r--r--app/src/main/java/app/passwordstore/ui/git/config/GitConfigActivity.kt176
-rw-r--r--app/src/main/java/app/passwordstore/ui/git/log/GitLogActivity.kt49
-rw-r--r--app/src/main/java/app/passwordstore/ui/git/log/GitLogAdapter.kt65
-rw-r--r--app/src/main/java/app/passwordstore/ui/main/MainActivity.kt8
-rw-r--r--app/src/main/java/app/passwordstore/ui/proxy/ProxySelectorActivity.kt109
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/AutofillSettings.kt114
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/GeneralSettings.kt120
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/MiscSettings.kt76
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/PGPSettings.kt136
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/PasswordSettings.kt48
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt193
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt129
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsProvider.kt15
-rw-r--r--app/src/main/java/app/passwordstore/ui/setup/SetupGraph.kt2
-rw-r--r--app/src/main/java/app/passwordstore/util/git/GitLogModel.kt60
16 files changed, 4 insertions, 1367 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/TextInputDialog.kt b/app/src/main/java/app/passwordstore/ui/dialogs/TextInputDialog.kt
deleted file mode 100644
index 58c39968..00000000
--- a/app/src/main/java/app/passwordstore/ui/dialogs/TextInputDialog.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.dialogs
-
-import android.app.Dialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.setFragmentResult
-import app.passwordstore.databinding.DialogTextInputBinding
-import app.passwordstore.util.extensions.finish
-import app.passwordstore.util.extensions.unsafeLazy
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-
-/**
- * General purpose [DialogFragment] that can be used to accept a single text input. It provides a
- * configurable title and text input hint through the instantiation helper at
- * [TextInputDialog.newInstance]. Typical usage would look something like this:
- * ```kotlin
- * val dialog = TextInputDialog.newInstance(getString(R.string.dialog_title), getString(R.string.dialog_hint))
- * dialog.show(supportFragmentManager, "text_input_dialog")
- * dialog.setFragmentResultListener(TextInputDialog.REQUEST_KEY) { _, bundle ->
- * doSomething(bundle.getString(TextInputDialog.BUNDLE_KEY_TEXT))
- * }
- * ```
- */
-class TextInputDialog : DialogFragment() {
- private val binding by unsafeLazy { DialogTextInputBinding.inflate(layoutInflater) }
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = MaterialAlertDialogBuilder(requireContext())
- builder.setView(binding.root)
- builder.setTitle(arguments?.getString(BUNDLE_KEY_TITLE))
- arguments?.getString(BUNDLE_KEY_HINT)?.let { hint ->
- binding.textInputLayout.isHintEnabled = true
- binding.textInputLayout.isHintAnimationEnabled = true
- binding.textInputLayout.hint = hint
- }
- builder.setPositiveButton(android.R.string.ok) { dialogInterface, _ ->
- setFragmentResult(REQUEST_KEY, bundleOf(BUNDLE_KEY_TEXT to binding.editText.text.toString()))
- dialogInterface.dismiss()
- }
- val dialog = builder.create()
- dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- return dialog
- }
-
- override fun onCancel(dialog: DialogInterface) {
- super.onCancel(dialog)
- finish()
- }
-
- companion object {
- const val REQUEST_KEY = "text_input_dialog"
- const val BUNDLE_KEY_TEXT = "text"
- private const val BUNDLE_KEY_TITLE = "title"
- private const val BUNDLE_KEY_HINT = "hint"
-
- fun newInstance(title: String, hint: String? = null): TextInputDialog {
- val args = bundleOf(BUNDLE_KEY_TITLE to title, BUNDLE_KEY_HINT to hint)
- val dialog = TextInputDialog()
- dialog.arguments = args
- return dialog
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/git/config/GitConfigActivity.kt b/app/src/main/java/app/passwordstore/ui/git/config/GitConfigActivity.kt
deleted file mode 100644
index 695029d4..00000000
--- a/app/src/main/java/app/passwordstore/ui/git/config/GitConfigActivity.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.ui.git.config
-
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.util.Patterns
-import android.view.MenuItem
-import androidx.core.os.postDelayed
-import androidx.fragment.app.setFragmentResultListener
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.R
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.databinding.ActivityGitConfigBinding
-import app.passwordstore.ui.dialogs.TextInputDialog
-import app.passwordstore.ui.git.base.BaseGitActivity
-import app.passwordstore.ui.git.log.GitLogActivity
-import app.passwordstore.util.extensions.asLog
-import app.passwordstore.util.extensions.launchActivity
-import app.passwordstore.util.extensions.viewBinding
-import com.github.michaelbull.result.fold
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
-import kotlinx.coroutines.launch
-import logcat.LogPriority.ERROR
-import logcat.logcat
-import org.eclipse.jgit.lib.Constants
-import org.eclipse.jgit.lib.Repository
-import org.eclipse.jgit.lib.RepositoryState
-
-class GitConfigActivity : BaseGitActivity() {
-
- private val binding by viewBinding(ActivityGitConfigBinding::inflate)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
-
- if (gitSettings.authorName.isEmpty()) binding.gitUserName.requestFocus()
- else binding.gitUserName.setText(gitSettings.authorName)
- binding.gitUserEmail.setText(gitSettings.authorEmail)
- setupTools()
- binding.saveButton.setOnClickListener {
- val email = binding.gitUserEmail.text.toString().trim()
- val name = binding.gitUserName.text.toString().trim()
- if (!email.matches(Patterns.EMAIL_ADDRESS.toRegex())) {
- MaterialAlertDialogBuilder(this)
- .setMessage(getString(R.string.invalid_email_dialog_text))
- .setPositiveButton(getString(R.string.dialog_ok), null)
- .show()
- } else {
- gitSettings.authorEmail = email
- gitSettings.authorName = name
- Snackbar.make(
- binding.root,
- getString(R.string.git_server_config_save_success),
- Snackbar.LENGTH_SHORT,
- )
- .show()
- Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
- }
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- android.R.id.home -> {
- onBackPressedDispatcher.onBackPressed()
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- /** Sets up the UI components of the tools section. */
- private fun setupTools() {
- val repo = PasswordRepository.repository
- if (repo != null) {
- binding.gitHeadStatus.text = headStatusMsg(repo)
- // enable the abort button only if we're rebasing or merging
- val needsAbort =
- repo.repositoryState.isRebasing || repo.repositoryState == RepositoryState.MERGING
- binding.gitAbortRebase.isEnabled = needsAbort
- binding.gitAbortRebase.alpha = if (needsAbort) 1.0f else 0.5f
- }
- binding.gitLog.setOnClickListener {
- runCatching { launchActivity(GitLogActivity::class.java) }
- .onFailure { ex -> logcat(ERROR) { ex.asLog("Failed to start GitLogActivity") } }
- }
- binding.gitAbortRebase.setOnClickListener { abortRebase() }
- binding.gitResetToRemote.setOnClickListener { resetToRemote() }
- binding.gitGc.setOnClickListener {
- lifecycleScope.launch {
- launchGitOperation(GitOp.GC)
- .fold(
- success = ::finishOnSuccessHandler,
- failure = { err -> promptOnErrorHandler(err) { finish() } },
- )
- }
- }
- }
-
- private fun resetToRemote() {
- val dialog =
- TextInputDialog.newInstance(getString(R.string.git_utils_reset_remote_branch_title))
- dialog.show(supportFragmentManager, "BRANCH_INPUT_DIALOG")
- dialog.setFragmentResultListener(TextInputDialog.REQUEST_KEY) { _, bundle ->
- val result = bundle.getString(TextInputDialog.BUNDLE_KEY_TEXT)
- if (!result.isNullOrEmpty()) {
- remoteBranch = result
- lifecycleScope.launch {
- launchGitOperation(GitOp.RESET)
- .fold(
- success = ::finishOnSuccessHandler,
- failure = { err -> promptOnErrorHandler(err) { finish() } },
- )
- }
- }
- }
- }
-
- private fun abortRebase() {
- lifecycleScope.launch {
- launchGitOperation(GitOp.BREAK_OUT_OF_DETACHED)
- .fold(
- success = {
- val branch = PasswordRepository.getCurrentBranch()
- MaterialAlertDialogBuilder(this@GitConfigActivity).run {
- setTitle(resources.getString(R.string.git_abort_and_push_title))
- setMessage(
- resources.getString(
- R.string.git_break_out_of_detached_success,
- branch,
- "conflicting-$branch-...",
- )
- )
- setOnDismissListener { finish() }
- setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> }
- show()
- }
- },
- failure = { err -> promptOnErrorHandler(err) { finish() } },
- )
- }
- }
-
- /**
- * Returns a user-friendly message about the current state of HEAD.
- *
- * The state is recognized to be either pointing to a branch or detached.
- */
- private fun headStatusMsg(repo: Repository): String {
- return runCatching {
- val headRef = repo.findRef(Constants.HEAD)
- if (headRef.isSymbolic) {
- val branchName = headRef.target.name
- val shortBranchName = Repository.shortenRefName(branchName)
- getString(R.string.git_head_on_branch, shortBranchName)
- } else {
- val commitHash = headRef.objectId.abbreviate(8).name()
- getString(R.string.git_head_detached, commitHash)
- }
- }
- .getOrElse { ex ->
- logcat(ERROR) { "Error getting HEAD reference\n${ex}" }
- getString(R.string.git_head_missing)
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/git/log/GitLogActivity.kt b/app/src/main/java/app/passwordstore/ui/git/log/GitLogActivity.kt
deleted file mode 100644
index a72881d1..00000000
--- a/app/src/main/java/app/passwordstore/ui/git/log/GitLogActivity.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.git.log
-
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import app.passwordstore.databinding.ActivityGitLogBinding
-import app.passwordstore.ui.git.base.BaseGitActivity
-import app.passwordstore.util.extensions.viewBinding
-
-/**
- * Displays the repository's git commits in git-log fashion.
- *
- * It provides basic information about each commit by way of a non-interactive RecyclerView.
- */
-class GitLogActivity : BaseGitActivity() {
-
- private val binding by viewBinding(ActivityGitLogBinding::inflate)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- createRecyclerView()
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- android.R.id.home -> {
- finish()
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- private fun createRecyclerView() {
- binding.gitLogRecyclerView.apply {
- setHasFixedSize(true)
- addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
- adapter = GitLogAdapter()
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/git/log/GitLogAdapter.kt b/app/src/main/java/app/passwordstore/ui/git/log/GitLogAdapter.kt
deleted file mode 100644
index 95186188..00000000
--- a/app/src/main/java/app/passwordstore/ui/git/log/GitLogAdapter.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.git.log
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import app.passwordstore.databinding.GitLogRowLayoutBinding
-import app.passwordstore.util.git.GitCommit
-import app.passwordstore.util.git.GitLogModel
-import java.time.Instant
-import java.time.ZoneId
-import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle
-import java.util.Locale
-import logcat.LogPriority.ERROR
-import logcat.logcat
-
-private fun shortHash(hash: String): String {
- return hash.substring(0 until 8)
-}
-
-private fun stringFrom(date: Instant): String {
- return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
- .withLocale(Locale.getDefault())
- .withZone(ZoneId.systemDefault())
- .format(date)
-}
-
-/** @see GitLogActivity */
-class GitLogAdapter : RecyclerView.Adapter<GitLogAdapter.ViewHolder>() {
-
- private val model = GitLogModel()
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val inflater = LayoutInflater.from(parent.context)
- val binding = GitLogRowLayoutBinding.inflate(inflater, parent, false)
- return ViewHolder(binding)
- }
-
- override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
- val commit = model.get(position)
- if (commit == null) {
- logcat(ERROR) { "There is no git commit for view holder at position $position." }
- return
- }
- viewHolder.bind(commit)
- }
-
- override fun getItemCount() = model.size
-
- class ViewHolder(private val binding: GitLogRowLayoutBinding) :
- RecyclerView.ViewHolder(binding.root) {
-
- fun bind(commit: GitCommit) =
- with(binding) {
- gitLogRowMessage.text = commit.shortMessage
- gitLogRowHash.text = shortHash(commit.hash)
- gitLogRowTime.text = stringFrom(commit.time)
- }
- }
-}
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 4d618cca..75062f6b 100644
--- a/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt
@@ -258,13 +258,13 @@ class MainActivity : BaseGitActivity() {
private const val EXTRA_SETUP = "SETUP"
/**
- * Opens one setup screen in an instance of its own, for callers outside the graph (legacy
- * Settings, autofill, git operations) and for those waiting for a result (the entry view's
+ * Opens one setup screen in an instance of its own, for callers outside the graph (autofill,
+ * the git operations' "No SSH key" dialog) and for those waiting for a result (the entry view's
* missing key). Leaving that screen finishes the instance; the PGP keys screen sets RESULT_OK
* once a key was imported.
*/
- // ponytail: a second instance instead of in-graph navigation, until steps 6 and 7 move
- // Settings and autofill into the graph.
+ // ponytail: a second instance instead of in-graph navigation, until step 7 moves autofill
+ // into the graph and the git operations' dialogs become Compose dialogs.
fun setupIntent(context: Context, start: SetupStart): Intent =
Intent(context, MainActivity::class.java).putExtra(EXTRA_SETUP, start.name)
}
diff --git a/app/src/main/java/app/passwordstore/ui/proxy/ProxySelectorActivity.kt b/app/src/main/java/app/passwordstore/ui/proxy/ProxySelectorActivity.kt
deleted file mode 100644
index cb5fbf40..00000000
--- a/app/src/main/java/app/passwordstore/ui/proxy/ProxySelectorActivity.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.proxy
-
-import android.content.SharedPreferences
-import android.net.InetAddresses
-import android.os.Build
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.util.Patterns
-import android.view.MenuItem
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.edit
-import androidx.core.os.postDelayed
-import androidx.core.widget.doOnTextChanged
-import app.passwordstore.R
-import app.passwordstore.databinding.ActivityProxySelectorBinding
-import app.passwordstore.injection.prefs.ProxyPreferences
-import app.passwordstore.util.extensions.getString
-import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.proxy.ProxyUtils
-import app.passwordstore.util.settings.GitSettings
-import app.passwordstore.util.settings.PreferenceKeys
-import dagger.hilt.android.AndroidEntryPoint
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class ProxySelectorActivity : AppCompatActivity() {
-
- @Inject lateinit var gitSettings: GitSettings
- @ProxyPreferences @Inject lateinit var proxyPrefs: SharedPreferences
- @Inject lateinit var proxyUtils: ProxyUtils
- private val binding by viewBinding(ActivityProxySelectorBinding::inflate)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- with(binding) {
- proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST))
- proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME))
- proxyPrefs
- .getInt(PreferenceKeys.PROXY_PORT, -1)
- .takeIf { it != -1 }
- ?.let { proxyPort.setText("$it") }
- proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD))
- save.setOnClickListener { saveSettings() }
- proxyHost.doOnTextChanged { text, _, _, _ ->
- if (text != null) {
- proxyHost.error =
- if (isNumericAddress(text) || text.matches(WEB_ADDRESS_REGEX)) {
- null
- } else {
- getString(R.string.invalid_proxy_url)
- }
- }
- }
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> {
- onBackPressedDispatcher.onBackPressed()
- }
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- private fun isNumericAddress(text: CharSequence): Boolean {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- InetAddresses.isNumericAddress(text as String)
- } else {
- @Suppress("DEPRECATION") Patterns.IP_ADDRESS.matcher(text).matches()
- }
- }
-
- private fun saveSettings() {
- proxyPrefs.edit {
- binding.proxyHost.text
- ?.toString()
- ?.takeIf { it.isNotEmpty() }
- .let { gitSettings.proxyHost = it }
- binding.proxyUser.text
- ?.toString()
- ?.takeIf { it.isNotEmpty() }
- .let { gitSettings.proxyUsername = it }
- binding.proxyPort.text
- ?.toString()
- ?.takeIf { it.isNotEmpty() }
- ?.let { gitSettings.proxyPort = it.toInt() }
- binding.proxyPassword.text
- ?.toString()
- ?.takeIf { it.isNotEmpty() }
- .let { gitSettings.proxyPassword = it }
- }
- proxyUtils.setDefaultProxy()
- Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
- }
-
- private companion object {
- private val WEB_ADDRESS_REGEX = Patterns.WEB_URL.toRegex()
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/AutofillSettings.kt b/app/src/main/java/app/passwordstore/ui/settings/AutofillSettings.kt
deleted file mode 100644
index afb2a691..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/AutofillSettings.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.provider.Settings
-import androidx.appcompat.widget.AppCompatTextView
-import androidx.core.net.toUri
-import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import app.passwordstore.BuildConfig
-import app.passwordstore.R
-import app.passwordstore.util.extensions.autofillManager
-import app.passwordstore.util.settings.PreferenceKeys
-import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel
-import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import de.Maxr1998.modernpreferences.PreferenceScreen
-import de.Maxr1998.modernpreferences.helpers.editText
-import de.Maxr1998.modernpreferences.helpers.onClick
-import de.Maxr1998.modernpreferences.helpers.switch
-import de.Maxr1998.modernpreferences.preferences.SwitchPreference
-
-class AutofillSettings(private val activity: FragmentActivity) : SettingsProvider {
-
- private val isAutofillServiceEnabled: Boolean
- get() {
- return activity.autofillManager?.hasEnabledAutofillServices() == true
- }
-
- private fun showAutofillDialog(pref: SwitchPreference) {
- val observer = LifecycleEventObserver { _, event ->
- when (event) {
- Lifecycle.Event.ON_RESUME -> {
- pref.checked = isAutofillServiceEnabled
- }
- else -> {}
- }
- }
- MaterialAlertDialogBuilder(activity).run {
- setTitle(R.string.pref_autofill_enable_title)
- @SuppressLint("InflateParams")
- val layout = activity.layoutInflater.inflate(R.layout.oreo_autofill_instructions, null)
- val supportedBrowsersTextView = layout.findViewById<AppCompatTextView>(R.id.supportedBrowsers)
- supportedBrowsersTextView.text =
- getInstalledBrowsersWithAutofillSupportLevel(context).joinToString(separator = "\n") {
- val appLabel = it.first
- val supportDescription =
- when (it.second) {
- BrowserAutofillSupportLevel.None ->
- activity.getString(R.string.oreo_autofill_no_support)
- BrowserAutofillSupportLevel.FlakyFill ->
- activity.getString(R.string.oreo_autofill_flaky_fill_support)
- BrowserAutofillSupportLevel.PasswordFill ->
- activity.getString(R.string.oreo_autofill_password_fill_support)
- BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility ->
- activity.getString(
- R.string.oreo_autofill_password_fill_and_conditional_save_support
- )
- BrowserAutofillSupportLevel.GeneralFill ->
- activity.getString(R.string.oreo_autofill_general_fill_support)
- BrowserAutofillSupportLevel.GeneralFillAndSave ->
- activity.getString(R.string.oreo_autofill_general_fill_and_save_support)
- }
- "$appLabel: $supportDescription"
- }
- setView(layout)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
- val intent =
- Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE).apply {
- data = "package:${BuildConfig.APPLICATION_ID}".toUri()
- }
- activity.startActivity(intent)
- }
- setNegativeButton(R.string.dialog_cancel, null)
- setOnDismissListener { pref.checked = isAutofillServiceEnabled }
- activity.lifecycle.addObserver(observer)
- show()
- }
- }
-
- override fun provideSettings(builder: PreferenceScreen.Builder) {
- builder.apply {
- switch(PreferenceKeys.AUTOFILL_ENABLE) {
- titleRes = R.string.pref_autofill_enable_title
- defaultValue = isAutofillServiceEnabled
- onClick {
- if (isAutofillServiceEnabled) {
- activity.autofillManager?.disableAutofillServices()
- } else {
- showAutofillDialog(this)
- }
- false
- }
- }
- editText(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME) {
- dependency = PreferenceKeys.AUTOFILL_ENABLE
- titleRes = R.string.preference_default_username_title
- summaryProvider = { activity.getString(R.string.preference_default_username_summary) }
- }
- editText(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES) {
- dependency = PreferenceKeys.AUTOFILL_ENABLE
- titleRes = R.string.preference_custom_public_suffixes_title
- summaryProvider = { activity.getString(R.string.preference_custom_public_suffixes_summary) }
- textInputHintRes = R.string.preference_custom_public_suffixes_hint
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/GeneralSettings.kt b/app/src/main/java/app/passwordstore/ui/settings/GeneralSettings.kt
deleted file mode 100644
index e3594269..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/GeneralSettings.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import android.content.pm.ShortcutManager
-import android.os.Build
-import androidx.core.content.edit
-import androidx.core.content.getSystemService
-import androidx.fragment.app.FragmentActivity
-import app.passwordstore.R
-import app.passwordstore.util.auth.BiometricAuthenticator
-import app.passwordstore.util.auth.BiometricAuthenticator.Result
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.settings.DirectoryStructure
-import app.passwordstore.util.settings.PreferenceKeys
-import de.Maxr1998.modernpreferences.PreferenceScreen
-import de.Maxr1998.modernpreferences.helpers.onClick
-import de.Maxr1998.modernpreferences.helpers.singleChoice
-import de.Maxr1998.modernpreferences.helpers.switch
-import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
-
-class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider {
-
- override fun provideSettings(builder: PreferenceScreen.Builder) {
- builder.apply {
- val themeValues = activity.resources.getStringArray(R.array.app_theme_values)
- val themeOptions = activity.resources.getStringArray(R.array.app_theme_options)
- val themeItems =
- themeValues.zip(themeOptions).map { SelectionItem(it.first, it.second, null) }
- singleChoice(PreferenceKeys.APP_THEME, themeItems) {
- initialSelection = activity.resources.getString(R.string.app_theme_def)
- titleRes = R.string.pref_app_theme_title
- }
-
- val values = activity.resources.getStringArray(R.array.directory_structure_values)
- val titles = activity.resources.getStringArray(R.array.directory_structure_entries)
- val items = values.zip(titles).map { SelectionItem(it.first, it.second, null) }
- singleChoice(PreferenceKeys.DIRECTORY_STRUCTURE, items) {
- initialSelection = DirectoryStructure.DEFAULT.value
- titleRes = R.string.pref_directory_structure
- }
-
- val sortValues = activity.resources.getStringArray(R.array.sort_order_values)
- val sortOptions = activity.resources.getStringArray(R.array.sort_order_entries)
- val sortItems = sortValues.zip(sortOptions).map { SelectionItem(it.first, it.second, null) }
- singleChoice(PreferenceKeys.SORT_ORDER, sortItems) {
- initialSelection = sortValues[0]
- titleRes = R.string.pref_sort_order_title
- }
-
- switch(PreferenceKeys.DISABLE_SYNC_ACTION) {
- titleRes = R.string.pref_disable_sync_on_pull_title
- summaryRes = R.string.pref_disable_sync_on_pull_summary
- defaultValue = false
- }
-
- switch(PreferenceKeys.FILTER_RECURSIVELY) {
- titleRes = R.string.pref_recursive_filter_title
- summaryRes = R.string.pref_recursive_filter_summary
- defaultValue = true
- }
-
- switch(PreferenceKeys.SEARCH_ON_START) {
- titleRes = R.string.pref_search_on_start_title
- summaryRes = R.string.pref_search_on_start_summary
- defaultValue = false
- }
-
- switch(PreferenceKeys.SHOW_HIDDEN_CONTENTS) {
- titleRes = R.string.pref_show_hidden_title
- summaryRes = R.string.pref_show_hidden_summary
- defaultValue = false
- }
-
- // See https://github.com/android-password-store/Android-Password-Store/issues/2802
- val disableAuth = Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- val canAuthenticate = BiometricAuthenticator.canAuthenticate(activity)
- switch(PreferenceKeys.BIOMETRIC_AUTH_2) {
- titleRes = R.string.pref_biometric_auth_title
- defaultValue = false
- enabled = !disableAuth
- summaryRes =
- if (disableAuth) R.string.pref_biometric_auth_summary_disabled_platform
- else if (canAuthenticate) R.string.pref_biometric_auth_summary
- else R.string.pref_biometric_auth_summary_error
- onClick {
- enabled = false
- val isChecked = checked
- activity.sharedPrefs.edit {
- BiometricAuthenticator.authenticate(activity) { result ->
- when (result) {
- is Result.Success -> {
- // Apply the changes
- putBoolean(PreferenceKeys.BIOMETRIC_AUTH_2, checked)
- enabled = true
- }
- is Result.Retry -> {}
- else -> {
- // If any error occurs, revert back to the previous
- // state. This
- // catch-all clause includes the cancellation case.
- putBoolean(PreferenceKeys.BIOMETRIC_AUTH_2, !checked)
- checked = !isChecked
- enabled = true
- }
- }
- }
- }
- activity.getSystemService<ShortcutManager>()?.apply {
- removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
- }
- false
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/MiscSettings.kt b/app/src/main/java/app/passwordstore/ui/settings/MiscSettings.kt
deleted file mode 100644
index 9a5d1e3b..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/MiscSettings.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.documentfile.provider.DocumentFile
-import androidx.fragment.app.FragmentActivity
-import app.passwordstore.BuildConfig
-import app.passwordstore.R
-import app.passwordstore.util.services.PasswordExportService
-import app.passwordstore.util.settings.PreferenceKeys
-import de.Maxr1998.modernpreferences.PreferenceScreen
-import de.Maxr1998.modernpreferences.helpers.onClick
-import de.Maxr1998.modernpreferences.helpers.pref
-import de.Maxr1998.modernpreferences.helpers.switch
-
-class MiscSettings(activity: FragmentActivity) : SettingsProvider {
-
- private val storeExportAction =
- activity.registerForActivityResult(
- object : ActivityResultContracts.OpenDocumentTree() {
- override fun createIntent(context: Context, input: Uri?): Intent {
- return super.createIntent(context, input).apply {
- flags =
- Intent.FLAG_GRANT_READ_URI_PERMISSION or
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
- Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
- Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
- }
- }
- }
- ) { uri: Uri? ->
- if (uri == null) return@registerForActivityResult
- val targetDirectory = DocumentFile.fromTreeUri(activity.applicationContext, uri)
-
- if (targetDirectory != null) {
- val service =
- Intent(activity.applicationContext, PasswordExportService::class.java).apply {
- action = PasswordExportService.ACTION_EXPORT_PASSWORD
- putExtra("uri", uri)
- }
-
- activity.startForegroundService(service)
- }
- }
-
- override fun provideSettings(builder: PreferenceScreen.Builder) {
- builder.apply {
- pref(PreferenceKeys.EXPORT_PASSWORDS) {
- titleRes = R.string.prefs_export_passwords_title
- summaryRes = R.string.prefs_export_passwords_summary
- onClick {
- storeExportAction.launch(null)
- true
- }
- }
- switch(PreferenceKeys.CLEAR_CLIPBOARD_HISTORY) {
- defaultValue = false
- titleRes = R.string.pref_clear_clipboard_title
- summaryRes = R.string.pref_clear_clipboard_summary
- }
- switch(PreferenceKeys.ENABLE_DEBUG_LOGGING) {
- defaultValue = false
- titleRes = R.string.pref_debug_logging_title
- summaryRes = R.string.pref_debug_logging_summary
- visible = !BuildConfig.DEBUG
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/PGPSettings.kt b/app/src/main/java/app/passwordstore/ui/settings/PGPSettings.kt
deleted file mode 100644
index 46e62faa..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/PGPSettings.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import androidx.core.content.edit
-import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.R
-import app.passwordstore.data.crypto.PGPPassphraseCache
-import app.passwordstore.ui.main.MainActivity
-import app.passwordstore.ui.setup.SetupStart
-import app.passwordstore.util.auth.BiometricAuthenticator
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.features.Feature
-import app.passwordstore.util.settings.PreferenceKeys
-import de.Maxr1998.modernpreferences.PreferenceScreen
-import de.Maxr1998.modernpreferences.helpers.onClick
-import de.Maxr1998.modernpreferences.helpers.pref
-import de.Maxr1998.modernpreferences.helpers.switch
-import kotlinx.coroutines.launch
-
-class PGPSettings(
- private val activity: FragmentActivity,
- private val passphraseCache: PGPPassphraseCache,
-) : SettingsProvider {
-
- override fun provideSettings(builder: PreferenceScreen.Builder) {
- builder.apply {
- pref("_") {
- titleRes = R.string.pref_pgp_key_manager_title
- persistent = false
- onClick {
- activity.startActivity(MainActivity.setupIntent(activity, SetupStart.PgpKeys))
- false
- }
- }
- switch(PreferenceKeys.ASCII_ARMOR) {
- titleRes = R.string.pref_pgp_ascii_armor_title
- persistent = true
- }
- switch(Feature.EnablePGPPassphraseCache.configKey) {
- enabled = BiometricAuthenticator.canAuthenticate(activity)
- titleRes = R.string.pref_passphrase_cache_title
- summaryRes = R.string.pref_passphrase_cache_summary
- defaultValue = false
- onClick {
- if (BiometricAuthenticator.canAuthenticate(activity)) {
- val promptTitle =
- if (checked) R.string.pref_passphrase_cache_authenticate_enable
- else R.string.pref_passphrase_cache_authenticate_disable
-
- BiometricAuthenticator.authenticate(activity, promptTitle) { result ->
- when (result) {
- is BiometricAuthenticator.Result.Success -> {
- /* Any successful change of this setting clears the passphrase
- * cache for safety */
- activity.lifecycleScope.launch {
- passphraseCache.clearAllCachedPassphrases(activity)
- }
- activity.sharedPrefs.edit {
- putBoolean(Feature.EnablePGPPassphraseCache.configKey, checked)
- if (!checked) remove(PreferenceKeys.CLEAR_PASSPHRASE_CACHE)
- }
- }
- is BiometricAuthenticator.Result.Retry -> {}
- else -> {
- /* revert back to previous state in case of error or cancellation */
- checked = !checked
- activity.sharedPrefs.edit {
- putBoolean(Feature.EnablePGPPassphraseCache.configKey, checked)
- }
- }
- }
- }
- } else {
- /* we may get here if device lock has been disabled while PGP settings
- * screen was left open */
- checked = false
- enabled = false
- activity.sharedPrefs.edit {
- putBoolean(Feature.EnablePGPPassphraseCache.configKey, false)
- }
- }
- false
- }
- }
- switch(PreferenceKeys.CLEAR_PASSPHRASE_CACHE) {
- dependency = Feature.EnablePGPPassphraseCache.configKey
- titleRes = R.string.pref_passphrase_cache_auto_clear_title
- summaryRes = R.string.pref_passphrase_cache_auto_clear_summary
- defaultValue = true
- /* Clear the cache once when unchecking; this is to prevent a malicious user (someone
- * knowing the screen-lock pin, but not knowing the PGP passphrase) from bypassing cache
- * clearing via the settings. Disabling it requires authentication, and cache clearing
- * stays enabled in case of any authentication failure. */
- onClick {
- if (!checked) {
- if (
- BiometricAuthenticator.canAuthenticate(activity) &&
- activity.sharedPrefs.getBoolean(Feature.EnablePGPPassphraseCache.configKey, false)
- ) {
- BiometricAuthenticator.authenticate(
- activity,
- R.string.pref_passphrase_cache_auto_clear_authenticate_disable,
- ) { result ->
- when (result) {
- is BiometricAuthenticator.Result.Success -> {
- activity.sharedPrefs.edit {
- putBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, false)
- }
- activity.lifecycleScope.launch {
- passphraseCache.clearAllCachedPassphrases(activity)
- }
- }
- is BiometricAuthenticator.Result.Retry -> {}
- else -> {
- activity.sharedPrefs.edit { remove(PreferenceKeys.CLEAR_PASSPHRASE_CACHE) }
- checked = true
- }
- }
- }
- } else {
- activity.sharedPrefs.edit { remove(PreferenceKeys.CLEAR_PASSPHRASE_CACHE) }
- checked = true
- enabled = false
- }
- }
- false
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/PasswordSettings.kt b/app/src/main/java/app/passwordstore/ui/settings/PasswordSettings.kt
deleted file mode 100644
index 9fe4cd42..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/PasswordSettings.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import android.text.InputType
-import androidx.fragment.app.FragmentActivity
-import app.passwordstore.R
-import app.passwordstore.util.settings.PreferenceKeys
-import de.Maxr1998.modernpreferences.PreferenceScreen
-import de.Maxr1998.modernpreferences.helpers.editText
-import de.Maxr1998.modernpreferences.helpers.singleChoice
-import de.Maxr1998.modernpreferences.helpers.switch
-import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
-
-class PasswordSettings(private val activity: FragmentActivity) : SettingsProvider {
-
- override fun provideSettings(builder: PreferenceScreen.Builder) {
- builder.apply {
- val values = activity.resources.getStringArray(R.array.pwgen_provider_values)
- val labels = activity.resources.getStringArray(R.array.pwgen_provider_labels)
- val items = values.zip(labels).map { SelectionItem(it.first, it.second, null) }
- singleChoice(PreferenceKeys.PREF_KEY_PWGEN_TYPE, items) {
- initialSelection = "diceware"
- titleRes = R.string.pref_password_generator_type_title
- }
- editText(PreferenceKeys.GENERAL_SHOW_TIME) {
- titleRes = R.string.pref_clipboard_timeout_title
- summaryProvider = { timeout ->
- activity.getString(R.string.pref_clipboard_timeout_summary, timeout ?: "45")
- }
- textInputType = InputType.TYPE_CLASS_NUMBER
- }
- switch(PreferenceKeys.SHOW_PASSWORD) {
- titleRes = R.string.show_password_pref_title
- summaryRes = R.string.show_password_pref_summary
- defaultValue = true
- }
- switch(PreferenceKeys.COPY_ON_DECRYPT) {
- titleRes = R.string.pref_copy_title
- summaryRes = R.string.pref_copy_summary
- defaultValue = false
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt b/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
deleted file mode 100644
index 23dd2829..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import android.content.SharedPreferences
-import android.content.pm.ShortcutManager
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.core.content.edit
-import androidx.core.content.getSystemService
-import androidx.fragment.app.FragmentActivity
-import app.passwordstore.R
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.injection.prefs.GitPreferences
-import app.passwordstore.ui.git.config.GitConfigActivity
-import app.passwordstore.ui.main.MainActivity
-import app.passwordstore.ui.proxy.ProxySelectorActivity
-import app.passwordstore.ui.setup.SetupStart
-import app.passwordstore.util.extensions.getString
-import app.passwordstore.util.extensions.launchActivity
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.extensions.snackbar
-import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.git.sshj.SshKey
-import app.passwordstore.util.settings.GitSettings
-import app.passwordstore.util.settings.PreferenceKeys
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dagger.hilt.EntryPoint
-import dagger.hilt.InstallIn
-import dagger.hilt.android.EntryPointAccessors
-import dagger.hilt.components.SingletonComponent
-import de.Maxr1998.modernpreferences.Preference
-import de.Maxr1998.modernpreferences.PreferenceScreen
-import de.Maxr1998.modernpreferences.helpers.onClick
-import de.Maxr1998.modernpreferences.helpers.pref
-import de.Maxr1998.modernpreferences.helpers.switch
-
-class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider {
-
- private val generateSshKey =
- activity.registerForActivityResult(StartActivityForResult()) {
- showSshKeyPref?.visible = SshKey.canShowSshPublicKey
- }
-
- private val hiltEntryPoint by unsafeLazy {
- EntryPointAccessors.fromApplication(
- activity.applicationContext,
- RepositorySettingsEntryPoint::class.java,
- )
- }
-
- private var showSshKeyPref: Preference? = null
-
- override fun provideSettings(builder: PreferenceScreen.Builder) {
- val encryptedPreferences = hiltEntryPoint.encryptedPreferences()
- val gitSettings = hiltEntryPoint.gitSettings()
-
- builder.apply {
- switch(PreferenceKeys.REBASE_ON_PULL) {
- titleRes = R.string.pref_rebase_on_pull_title
- summaryRes = R.string.pref_rebase_on_pull_summary
- summaryOnRes = R.string.pref_rebase_on_pull_summary_on
- defaultValue = true
- }
- pref(PreferenceKeys.GIT_SERVER_INFO) {
- titleRes = R.string.pref_edit_git_server_settings
- visible = PasswordRepository.isGitRepo()
- onClick {
- activity.startActivity(MainActivity.setupIntent(activity, SetupStart.GitServer))
- true
- }
- }
- pref(PreferenceKeys.PROXY_SETTINGS) {
- titleRes = R.string.pref_edit_proxy_settings
- visible = gitSettings.url?.startsWith("https") == true && PasswordRepository.isGitRepo()
- onClick {
- activity.launchActivity(ProxySelectorActivity::class.java)
- true
- }
- }
- pref(PreferenceKeys.GIT_CONFIG) {
- titleRes = R.string.pref_edit_git_config
- visible = PasswordRepository.isGitRepo()
- onClick {
- activity.launchActivity(GitConfigActivity::class.java)
- true
- }
- }
- pref(PreferenceKeys.SSH_KEY) {
- titleRes = R.string.pref_import_ssh_key_title
- visible = PasswordRepository.isGitRepo()
- onClick {
- generateSshKey.launch(MainActivity.setupIntent(activity, SetupStart.Ssh))
- true
- }
- }
- pref(PreferenceKeys.SSH_KEYGEN) {
- titleRes = R.string.pref_ssh_keygen_title
- onClick {
- generateSshKey.launch(MainActivity.setupIntent(activity, SetupStart.SshGenerate))
- true
- }
- }
- showSshKeyPref =
- pref(PreferenceKeys.SSH_SEE_KEY) {
- titleRes = R.string.pref_ssh_see_key_title
- visible = PasswordRepository.isGitRepo() && SshKey.canShowSshPublicKey
- onClick {
- generateSshKey.launch(MainActivity.setupIntent(activity, SetupStart.Ssh))
- true
- }
- }
- pref(PreferenceKeys.CLEAR_SAVED_PASS) {
- fun Preference.updatePref() {
- val sshPass = encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
- val httpsPass = encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD)
- if (sshPass == null && httpsPass == null) {
- visible = false
- return
- }
- titleRes =
- when {
- httpsPass != null -> R.string.clear_saved_passphrase_https
- else -> R.string.clear_saved_passphrase_ssh
- }
- visible = true
- requestRebind()
- }
- onClick {
- updatePref()
- true
- }
- updatePref()
- }
- pref(PreferenceKeys.SSH_OPENKEYSTORE_CLEAR_KEY_ID) {
- titleRes = R.string.pref_title_openkeystore_clear_keyid
- visible =
- activity.sharedPrefs.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty()
- ?: false
- onClick {
- activity.sharedPrefs.edit { putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null) }
- visible = false
- true
- }
- }
- pref(PreferenceKeys.GIT_DELETE_REPO) {
- titleRes = R.string.pref_git_delete_repo_title
- summaryRes = R.string.pref_git_delete_repo_summary
- onClick {
- val repoDir = PasswordRepository.getRepositoryDirectory()
- MaterialAlertDialogBuilder(activity)
- .setTitle(R.string.pref_dialog_delete_title)
- .setMessage(activity.getString(R.string.dialog_delete_msg, repoDir))
- .setCancelable(false)
- .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
- runCatching {
- PasswordRepository.closeRepository()
- PasswordRepository.getRepositoryDirectory().let { dir ->
- dir.deleteRecursively()
- dir.mkdirs()
- }
- }
- .onFailure { it.message?.let { message -> activity.snackbar(message = message) } }
-
- activity.getSystemService<ShortcutManager>()?.apply {
- removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
- }
- activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) }
- dialogInterface.cancel()
- activity.finish()
- }
- .setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ ->
- run { dialogInterface.cancel() }
- }
- .show()
- true
- }
- }
- }
- }
-
- @EntryPoint
- @InstallIn(SingletonComponent::class)
- interface RepositorySettingsEntryPoint {
- fun gitSettings(): GitSettings
-
- @GitPreferences fun encryptedPreferences(): SharedPreferences
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
deleted file mode 100644
index 02addd84..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.activity.OnBackPressedCallback
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.os.BundleCompat
-import app.passwordstore.R
-import app.passwordstore.data.crypto.PGPPassphraseCache
-import app.passwordstore.databinding.ActivityPreferenceRecyclerviewBinding
-import app.passwordstore.util.extensions.viewBinding
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dagger.hilt.android.AndroidEntryPoint
-import de.Maxr1998.modernpreferences.Preference
-import de.Maxr1998.modernpreferences.PreferencesAdapter
-import de.Maxr1998.modernpreferences.helpers.screen
-import de.Maxr1998.modernpreferences.helpers.subScreen
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class SettingsActivity : AppCompatActivity() {
-
- @Inject lateinit var passphraseCache: PGPPassphraseCache
- private val miscSettings = MiscSettings(this)
- private val autofillSettings = AutofillSettings(this)
- private val passwordSettings = PasswordSettings(this)
- private val repositorySettings = RepositorySettings(this)
- private val generalSettings = GeneralSettings(this)
- private lateinit var pgpSettings: PGPSettings
-
- private val binding by viewBinding(ActivityPreferenceRecyclerviewBinding::inflate)
- private val preferencesAdapter: PreferencesAdapter
- get() = binding.preferenceRecyclerView.adapter as PreferencesAdapter
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- Preference.Config.dialogBuilderFactory = { context -> MaterialAlertDialogBuilder(context) }
- pgpSettings = PGPSettings(this, passphraseCache)
- val screen =
- screen(this) {
- subScreen {
- collapseIcon = true
- titleRes = R.string.pref_category_general_title
- iconRes = R.drawable.app_settings_alt_24px
- generalSettings.provideSettings(this)
- }
- subScreen {
- collapseIcon = true
- titleRes = R.string.pref_category_autofill_title
- iconRes = R.drawable.ic_wysiwyg_24px
- autofillSettings.provideSettings(this)
- }
- subScreen {
- collapseIcon = true
- titleRes = R.string.pref_category_passwords_title
- iconRes = R.drawable.ic_password_24px
- passwordSettings.provideSettings(this)
- }
- subScreen {
- collapseIcon = true
- titleRes = R.string.pref_category_repository_title
- iconRes = R.drawable.ic_call_merge_24px
- repositorySettings.provideSettings(this)
- }
- subScreen {
- collapseIcon = true
- titleRes = R.string.pref_category_misc_title
- iconRes = R.drawable.ic_miscellaneous_services_24px
- miscSettings.provideSettings(this)
- }
- subScreen {
- collapseIcon = true
- titleRes = R.string.pref_category_pgp_title
- iconRes = R.drawable.ic_lock_open_24px
- pgpSettings.provideSettings(this)
- }
- }
- val backPressedCallback =
- object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- preferencesAdapter.goBack()
- }
- }
- onBackPressedDispatcher.addCallback(backPressedCallback)
- val adapter = PreferencesAdapter(screen)
- adapter.onScreenChangeListener =
- PreferencesAdapter.OnScreenChangeListener { subScreen, entering ->
- backPressedCallback.isEnabled = entering
- supportActionBar?.title =
- if (!entering) {
- getString(R.string.action_settings)
- } else {
- getString(subScreen.titleRes)
- }
- }
- if (savedInstanceState != null) {
- BundleCompat.getParcelable(
- savedInstanceState,
- "adapter",
- PreferencesAdapter.SavedState::class.java,
- )
- ?.let(adapter::loadSavedState)
- }
- binding.preferenceRecyclerView.adapter = adapter
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putParcelable("adapter", preferencesAdapter.getSavedState())
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- android.R.id.home ->
- if (!preferencesAdapter.goBack()) {
- super.onOptionsItemSelected(item)
- } else {
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsProvider.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsProvider.kt
deleted file mode 100644
index cacca80f..00000000
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsProvider.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.settings
-
-import de.Maxr1998.modernpreferences.PreferenceScreen
-
-/** Used to generate a uniform API for all settings UI classes. */
-interface SettingsProvider {
-
- /** Inserts the settings items for the class into the given [builder]. */
- fun provideSettings(builder: PreferenceScreen.Builder)
-}
diff --git a/app/src/main/java/app/passwordstore/ui/setup/SetupGraph.kt b/app/src/main/java/app/passwordstore/ui/setup/SetupGraph.kt
index c2c2a2ac..40dfae74 100644
--- a/app/src/main/java/app/passwordstore/ui/setup/SetupGraph.kt
+++ b/app/src/main/java/app/passwordstore/ui/setup/SetupGraph.kt
@@ -33,10 +33,8 @@ import kotlinx.serialization.Serializable
/** The setup screens [MainActivity.setupIntent] opens on their own. */
enum class SetupStart(val route: Any) {
- GitServer(GitServerRoute(clone = false)),
Ssh(SshKeyRoute),
SshGenerate(SshKeyGenRoute),
- PgpKeys(PgpKeysRoute()),
PgpImport(PgpKeysRoute(KeysMode.Need)),
}
diff --git a/app/src/main/java/app/passwordstore/util/git/GitLogModel.kt b/app/src/main/java/app/passwordstore/util/git/GitLogModel.kt
deleted file mode 100644
index 329db6d5..00000000
--- a/app/src/main/java/app/passwordstore/util/git/GitLogModel.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.util.git
-
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.util.extensions.asLog
-import app.passwordstore.util.extensions.hash
-import app.passwordstore.util.extensions.time
-import app.passwordstore.util.extensions.unsafeLazy
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.runCatching
-import logcat.LogPriority.ERROR
-import logcat.logcat
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.revwalk.RevCommit
-
-private val TAG = GitLogModel::class.java.simpleName
-
-private fun commits(): Iterable<RevCommit> {
- val repo = PasswordRepository.repository
- if (repo == null) {
- logcat(TAG, ERROR) { "Could not access git repository" }
- return listOf()
- }
- return runCatching { Git(repo).log().call() }
- .getOrElse { e ->
- logcat(TAG, ERROR) { e.asLog("Failed to obtain git commits") }
- listOf()
- }
-}
-
-/**
- * Provides [GitCommit] s from a git-log of the password git repository.
- *
- * All commits are acquired on the first request to this object.
- */
-class GitLogModel {
-
- // All commits are acquired here at once. Acquiring the commits in batches would not have been
- // entirely sensible because the amount of computation required to obtain commit number n from
- // the log includes the amount of computation required to obtain commit number n-1 from the log.
- // This is because the commit graph is walked from HEAD to the last commit to obtain.
- // Additionally, tests with 1000 commits in the log have not produced a significant delay in the
- // user experience.
- private val cache: MutableList<GitCommit> by unsafeLazy {
- commits()
- .map { GitCommit(it.hash, it.shortMessage, it.authorIdent.name, it.time) }
- .toMutableList()
- }
- val size = cache.size
-
- fun get(index: Int): GitCommit? {
- if (index >= size)
- logcat(ERROR) { "Cannot get git commit with index $index. There are only $size." }
- return cache.getOrNull(index)
- }
-}