diff --git a/CHANGELOG.md b/CHANGELOG.md
index 88686572..58e172d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ All notable changes to this project will be documented in this file.
### Changed
- Accessibility autofill has been removed completely due to being buggy, insecure and lacking in features. Upgrade to Android 8 or preferably later to gain access to our advanced Autofill implementation.
+- The settings UI has been completely re-done to dramatically improve discoverability and navigation for users
## [1.13.1] - 2020-10-23
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 90322274..a1b2583b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -73,6 +73,7 @@ dependencies {
implementation(Dependencies.FirstParty.zxing_android_embedded)
+ implementation(Dependencies.ThirdParty.bouncycastle)
implementation(Dependencies.ThirdParty.commons_codec)
implementation(Dependencies.ThirdParty.eddsa)
implementation(Dependencies.ThirdParty.fastscroll)
@@ -80,10 +81,10 @@ dependencies {
exclude(group = "org.apache.httpcomponents", module = "httpclient")
}
implementation(Dependencies.ThirdParty.kotlin_result)
- implementation(Dependencies.ThirdParty.sshj)
- implementation(Dependencies.ThirdParty.bouncycastle)
+ implementation(Dependencies.ThirdParty.modern_android_prefs)
implementation(Dependencies.ThirdParty.plumber)
implementation(Dependencies.ThirdParty.ssh_auth)
+ implementation(Dependencies.ThirdParty.sshj)
implementation(Dependencies.ThirdParty.timber)
implementation(Dependencies.ThirdParty.timberkt)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3e1d743e..69632359 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,7 +33,8 @@
android:name=".ui.onboarding.activity.OnboardingActivity"
android:configChanges="orientation|screenSize" />
-
+ android:name=".ui.settings.SettingsActivity"
+ android:label="@string/action_settings"
+ android:parentActivityName=".ui.passwords.PasswordStore" />
+
+
+
{
runCatching {
- startActivity(Intent(this, UserPreference::class.java))
+ startActivity(Intent(this, SettingsActivity::class.java))
}.onFailure { e ->
e.printStackTrace()
}
@@ -377,7 +378,7 @@ class PasswordStore : BaseGitActivity() {
private fun checkLocalRepository() {
val repo = PasswordRepository.initialize()
if (repo == null) {
- directorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this))
+ directorySelectAction.launch(Intent(this, DirectorySelectionActivity::class.java))
} else {
checkLocalRepository(PasswordRepository.getRepositoryDirectory())
}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt
new file mode 100644
index 00000000..74b407fd
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import de.Maxr1998.modernpreferences.PreferenceScreen
+import de.Maxr1998.modernpreferences.helpers.editText
+import de.Maxr1998.modernpreferences.helpers.onClick
+import de.Maxr1998.modernpreferences.helpers.singleChoice
+import de.Maxr1998.modernpreferences.helpers.switch
+import de.Maxr1998.modernpreferences.preferences.SwitchPreference
+import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
+import dev.msfjarvis.aps.BuildConfig
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.autofill.DirectoryStructure
+import dev.msfjarvis.aps.util.extensions.autofillManager
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel
+import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+class AutofillSettings(private val activity: FragmentActivity) : SettingsProvider {
+
+ private val isAutofillServiceEnabled: Boolean
+ get() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
+ return activity.autofillManager?.hasEnabledAutofillServices() == true
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ 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(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 = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
+ }
+ 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
+ visible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ defaultValue = isAutofillServiceEnabled
+ onClick {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return@onClick true
+ if (isAutofillServiceEnabled) {
+ activity.autofillManager?.disableAutofillServices()
+ } else {
+ showAutofillDialog(this)
+ }
+ false
+ }
+ }
+ val values = activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_values)
+ val titles = activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_entries)
+ val items = values.zip(titles).map { SelectionItem(it.first, it.second, null) }
+ singleChoice(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE, items) {
+ initialSelection = DirectoryStructure.DEFAULT.value
+ dependency = PreferenceKeys.AUTOFILL_ENABLE
+ titleRes = R.string.oreo_autofill_preference_directory_structure
+ }
+ 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/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt
new file mode 100644
index 00000000..41cd254b
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import android.net.Uri
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.os.Environment
+import android.provider.DocumentsContract
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.edit
+import com.github.ajalt.timberkt.d
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+class DirectorySelectionActivity : AppCompatActivity() {
+
+ @Suppress("DEPRECATION")
+ private val directorySelectAction = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri: Uri? ->
+ if (uri == null) return@registerForActivityResult
+
+ d { "Selected repository URI is $uri" }
+ // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile
+ val docId = DocumentsContract.getTreeDocumentId(uri)
+ val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ val path = if (split.size > 1) split[1] else split[0]
+ val repoPath = "${Environment.getExternalStorageDirectory()}/$path"
+ val prefs = sharedPrefs
+
+ d { "Selected repository path is $repoPath" }
+
+ if (Environment.getExternalStorageDirectory().path == repoPath) {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(resources.getString(R.string.sdcard_root_warning_title))
+ .setMessage(resources.getString(R.string.sdcard_root_warning_message))
+ .setPositiveButton(resources.getString(R.string.sdcard_root_warning_remove_everything)) { _, _ ->
+ prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) }
+ }
+ .setNegativeButton(R.string.dialog_cancel, null)
+ .show()
+ }
+ prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) }
+ setResult(RESULT_OK)
+ finish()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ directorySelectAction.launch(null)
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt
new file mode 100644
index 00000000..91df87d0
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import de.Maxr1998.modernpreferences.PreferenceScreen
+import de.Maxr1998.modernpreferences.helpers.checkBox
+import de.Maxr1998.modernpreferences.helpers.onClick
+import de.Maxr1998.modernpreferences.helpers.singleChoice
+import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import android.content.pm.ShortcutManager
+import android.os.Build
+import androidx.core.content.edit
+import androidx.core.content.getSystemService
+import androidx.fragment.app.FragmentActivity
+
+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 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
+ }
+
+ checkBox(PreferenceKeys.FILTER_RECURSIVELY) {
+ titleRes = R.string.pref_recursive_filter_title
+ summaryRes = R.string.pref_recursive_filter_summary
+ defaultValue = true
+ }
+
+ checkBox(PreferenceKeys.SEARCH_ON_START) {
+ titleRes = R.string.pref_search_on_start_title
+ summaryRes = R.string.pref_search_on_start_summary
+ defaultValue = false
+ }
+
+ checkBox(PreferenceKeys.SHOW_HIDDEN_CONTENTS) {
+ titleRes = R.string.pref_show_hidden_title
+ summaryRes = R.string.pref_show_hidden_summary
+ defaultValue = false
+ }
+
+ checkBox(PreferenceKeys.BIOMETRIC_AUTH) {
+ titleRes = R.string.pref_biometric_auth_title
+ defaultValue = false
+ }.apply {
+ val canAuthenticate = BiometricAuthenticator.canAuthenticate(activity)
+ if (!canAuthenticate) {
+ enabled = false
+ checked = false
+ summaryRes = R.string.pref_biometric_auth_summary_error
+ } else {
+ summaryRes = R.string.pref_biometric_auth_summary
+ onClick {
+ enabled = false
+ val isChecked = checked
+ activity.sharedPrefs.edit {
+ BiometricAuthenticator.authenticate(activity) { result ->
+ when (result) {
+ is BiometricAuthenticator.Result.Success -> {
+ // Apply the changes
+ putBoolean(PreferenceKeys.BIOMETRIC_AUTH, checked)
+ enabled = true
+ }
+ else -> {
+ // If any error occurs, revert back to the previous state. This
+ // catch-all clause includes the cancellation case.
+ putBoolean(PreferenceKeys.BIOMETRIC_AUTH, !checked)
+ checked = !isChecked
+ enabled = true
+ }
+ }
+ }
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ activity.getSystemService()?.apply {
+ removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
+ }
+ }
+ false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/MiscSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/MiscSettings.kt
new file mode 100644
index 00000000..08da760c
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/MiscSettings.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import de.Maxr1998.modernpreferences.PreferenceScreen
+import de.Maxr1998.modernpreferences.helpers.checkBox
+import de.Maxr1998.modernpreferences.helpers.onClick
+import de.Maxr1998.modernpreferences.helpers.pref
+import dev.msfjarvis.aps.BuildConfig
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.services.PasswordExportService
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.documentfile.provider.DocumentFile
+import androidx.fragment.app.FragmentActivity
+
+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)
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ activity.startForegroundService(service)
+ } else {
+ activity.startService(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
+ }
+ }
+ checkBox(PreferenceKeys.CLEAR_CLIPBOARD_20X) {
+ defaultValue = false
+ titleRes = R.string.pref_clear_clipboard_title
+ summaryRes = R.string.pref_clear_clipboard_summary
+ }
+ checkBox(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/dev/msfjarvis/aps/ui/settings/PasswordSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/PasswordSettings.kt
new file mode 100644
index 00000000..9b7eb01c
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/PasswordSettings.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import de.Maxr1998.modernpreferences.Preference
+import de.Maxr1998.modernpreferences.PreferenceScreen
+import de.Maxr1998.modernpreferences.helpers.categoryHeader
+import de.Maxr1998.modernpreferences.helpers.checkBox
+import de.Maxr1998.modernpreferences.helpers.editText
+import de.Maxr1998.modernpreferences.helpers.onCheckedChange
+import de.Maxr1998.modernpreferences.helpers.onClick
+import de.Maxr1998.modernpreferences.helpers.onSelectionChange
+import de.Maxr1998.modernpreferences.helpers.singleChoice
+import de.Maxr1998.modernpreferences.preferences.CheckBoxPreference
+import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.extensions.getString
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.pwgenxkpwd.XkpwdDictionary
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import android.text.InputType
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.edit
+import androidx.fragment.app.FragmentActivity
+import java.io.File
+
+class PasswordSettings(private val activity: FragmentActivity) : SettingsProvider {
+
+ private val sharedPrefs by lazy(LazyThreadSafetyMode.NONE) { activity.sharedPrefs }
+ private val storeCustomXkpwdDictionaryAction = activity.registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
+ if (uri == null) return@registerForActivityResult
+
+ Toast.makeText(
+ activity,
+ activity.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path),
+ Toast.LENGTH_SHORT
+ ).show()
+
+ sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, uri.toString()) }
+
+ val inputStream = activity.contentResolver.openInputStream(uri)
+ val customDictFile = File(activity.filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE).outputStream()
+ inputStream?.copyTo(customDictFile, 1024)
+ inputStream?.close()
+ customDictFile.close()
+ }
+
+ override fun provideSettings(builder: PreferenceScreen.Builder) {
+ builder.apply {
+ val customDictPref = CheckBoxPreference(PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT).apply {
+ titleRes = R.string.pref_xkpwgen_custom_wordlist_enabled_title
+ summaryRes = R.string.pref_xkpwgen_custom_dict_summary_off
+ summaryOnRes = R.string.pref_xkpwgen_custom_dict_summary_on
+ visible = sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) == "xkpasswd"
+ onCheckedChange {
+ requestRebind()
+ true
+ }
+ }
+ val customDictPathPref = Preference(PreferenceKeys.PREF_KEY_CUSTOM_DICT).apply {
+ dependency = PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT
+ titleRes = R.string.pref_xkpwgen_custom_dict_picker_title
+ summary = sharedPrefs.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT)
+ ?: activity.resources.getString(R.string.pref_xkpwgen_custom_dict_picker_summary)
+ visible = sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) == "xkpasswd"
+ onClick {
+ storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*"))
+ true
+ }
+ }
+ 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 = "classic"
+ titleRes = R.string.pref_password_generator_type_title
+ onSelectionChange { selection ->
+ val xkpasswdEnabled = selection == "xkpasswd"
+ customDictPathPref.visible = xkpasswdEnabled
+ customDictPref.visible = xkpasswdEnabled
+ customDictPref.requestRebind()
+ customDictPathPref.requestRebind()
+ true
+ }
+ }
+ // We initialize them early and add them manually to be able to manually force a rebind
+ // when the password generator type is changed.
+ addPreferenceItem(customDictPref)
+ addPreferenceItem(customDictPathPref)
+ editText(PreferenceKeys.GENERAL_SHOW_TIME) {
+ titleRes = R.string.pref_clipboard_timeout_title
+ summaryProvider = { activity.getString(R.string.pref_clipboard_timeout_summary) }
+ textInputType = InputType.TYPE_CLASS_NUMBER
+ }
+ checkBox(PreferenceKeys.SHOW_PASSWORD) {
+ titleRes = R.string.show_password_pref_title
+ summaryRes = R.string.show_password_pref_summary
+ defaultValue = true
+ }
+ checkBox(PreferenceKeys.SHOW_EXTRA_CONTENT) {
+ titleRes = R.string.show_extra_content_pref_title
+ summaryRes = R.string.show_extra_content_pref_summary
+ defaultValue = true
+ }
+ checkBox(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/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt
new file mode 100644
index 00000000..32d04c9b
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import de.Maxr1998.modernpreferences.Preference
+import de.Maxr1998.modernpreferences.PreferenceScreen
+import de.Maxr1998.modernpreferences.helpers.checkBox
+import de.Maxr1998.modernpreferences.helpers.onCheckedChange
+import de.Maxr1998.modernpreferences.helpers.onClick
+import de.Maxr1998.modernpreferences.helpers.pref
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.data.repo.PasswordRepository
+import dev.msfjarvis.aps.ui.git.config.GitConfigActivity
+import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity
+import dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity
+import dev.msfjarvis.aps.ui.sshkeygen.ShowSshKeyFragment
+import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity
+import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity
+import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs
+import dev.msfjarvis.aps.util.extensions.getString
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.extensions.snackbar
+import dev.msfjarvis.aps.util.settings.GitSettings
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import android.content.Intent
+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 com.github.michaelbull.result.onFailure
+import com.github.michaelbull.result.runCatching
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider {
+
+ private val encryptedPreferences by lazy(LazyThreadSafetyMode.NONE) { activity.getEncryptedGitPrefs() }
+
+ private fun launchActivity(clazz: Class) {
+ activity.startActivity(Intent(activity, clazz))
+ }
+
+ private fun selectExternalGitRepository() {
+ MaterialAlertDialogBuilder(activity)
+ .setTitle(activity.resources.getString(R.string.external_repository_dialog_title))
+ .setMessage(activity.resources.getString(R.string.external_repository_dialog_text))
+ .setPositiveButton(R.string.dialog_ok) { _, _ ->
+ launchActivity(DirectorySelectionActivity::class.java)
+ }
+ .setNegativeButton(R.string.dialog_cancel, null)
+ .show()
+ }
+
+ override fun provideSettings(builder: PreferenceScreen.Builder) {
+ builder.apply {
+ pref(PreferenceKeys.GIT_SERVER_INFO) {
+ titleRes = R.string.pref_edit_git_server_settings
+ visible = PasswordRepository.isGitRepo()
+ onClick {
+ launchActivity(GitServerConfigActivity::class.java)
+ true
+ }
+ }
+ pref(PreferenceKeys.PROXY_SETTINGS) {
+ titleRes = R.string.pref_edit_proxy_settings
+ visible = GitSettings.url?.startsWith("https") == true && PasswordRepository.isGitRepo()
+ onClick {
+ launchActivity(ProxySelectorActivity::class.java)
+ true
+ }
+ }
+ pref(PreferenceKeys.GIT_CONFIG) {
+ titleRes = R.string.pref_edit_git_config
+ visible = PasswordRepository.isGitRepo()
+ onClick {
+ launchActivity(GitConfigActivity::class.java)
+ true
+ }
+ }
+ pref(PreferenceKeys.SSH_KEY) {
+ titleRes = R.string.pref_import_ssh_key_title
+ visible = PasswordRepository.isGitRepo()
+ onClick {
+ launchActivity(SshKeyImportActivity::class.java)
+ true
+ }
+ }
+ pref(PreferenceKeys.SSH_KEYGEN) {
+ titleRes = R.string.pref_ssh_keygen_title
+ onClick {
+ launchActivity(SshKeyGenActivity::class.java)
+ true
+ }
+ }
+ pref(PreferenceKeys.SSH_SEE_KEY) {
+ titleRes = R.string.pref_ssh_see_key_title
+ visible = PasswordRepository.isGitRepo()
+ onClick {
+ ShowSshKeyFragment().show(activity.supportFragmentManager, "public_key")
+ 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
+ }
+ when {
+ httpsPass != null -> titleRes = R.string.clear_saved_passphrase_https
+ sshPass != null -> titleRes = 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
+ }
+ }
+ val deleteRepoPref = pref(PreferenceKeys.GIT_DELETE_REPO) {
+ titleRes = R.string.pref_git_delete_repo_title
+ summaryRes = R.string.pref_git_delete_repo_summary
+ visible = !activity.sharedPrefs.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
+ 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.getRepositoryDirectory().deleteRecursively()
+ PasswordRepository.closeRepository()
+ }.onFailure {
+ it.message?.let { message ->
+ activity.snackbar(message = message)
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ activity.getSystemService()?.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
+ }
+ }
+ checkBox(PreferenceKeys.GIT_EXTERNAL) {
+ titleRes = R.string.pref_external_repository_title
+ summaryRes = R.string.pref_external_repository_summary
+ onCheckedChange { checked ->
+ deleteRepoPref.visible = !checked
+ deleteRepoPref.requestRebind()
+ PasswordRepository.closeRepository()
+ activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPO_CHANGED, true) }
+ true
+ }
+ }
+ pref(PreferenceKeys.GIT_EXTERNAL_REPO) {
+ val externalRepo = activity.sharedPrefs.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
+ if (externalRepo != null) {
+ summary = externalRepo
+ } else {
+ summaryRes = R.string.pref_select_external_repository_summary_no_repo_selected
+ }
+ titleRes = R.string.pref_select_external_repository_title
+ dependency = PreferenceKeys.GIT_EXTERNAL
+ onClick {
+ selectExternalGitRepository()
+ true
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt
new file mode 100644
index 00000000..7db80023
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.settings
+
+import de.Maxr1998.modernpreferences.PreferencesAdapter
+import de.Maxr1998.modernpreferences.helpers.screen
+import de.Maxr1998.modernpreferences.helpers.subScreen
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.databinding.ActivityPreferenceRecyclerviewBinding
+import dev.msfjarvis.aps.util.extensions.viewBinding
+import android.os.Bundle
+import android.view.MenuItem
+import androidx.appcompat.app.AppCompatActivity
+
+class SettingsActivity : AppCompatActivity() {
+
+ 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 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)
+ val screen = screen(this) {
+ subScreen {
+ titleRes = R.string.pref_category_general_title
+ iconRes = R.drawable.app_settings_alt_24px
+ generalSettings.provideSettings(this)
+ }
+ subScreen {
+ titleRes = R.string.pref_category_autofill_title
+ iconRes = R.drawable.ic_wysiwyg_24px
+ autofillSettings.provideSettings(this)
+ }
+ subScreen {
+ titleRes = R.string.pref_category_passwords_title
+ iconRes = R.drawable.ic_lock_open_24px
+ passwordSettings.provideSettings(this)
+ }
+ subScreen {
+ titleRes = R.string.pref_category_repository_title
+ iconRes = R.drawable.ic_call_merge_24px
+ repositorySettings.provideSettings(this)
+ }
+ subScreen {
+ titleRes = R.string.pref_category_misc_title
+ iconRes = R.drawable.ic_miscellaneous_services_24px
+ miscSettings.provideSettings(this)
+ }
+ }
+ val adapter = PreferencesAdapter(screen)
+ adapter.onScreenChangeListener = PreferencesAdapter.OnScreenChangeListener { subScreen, entering ->
+ supportActionBar?.title = if (!entering) {
+ getString(R.string.action_settings)
+ } else {
+ getString(subScreen.titleRes)
+ }
+ }
+ savedInstanceState?.getParcelable("adapter")
+ ?.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)
+ }
+ }
+
+ override fun onBackPressed() {
+ if (!preferencesAdapter.goBack())
+ super.onBackPressed()
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsProvider.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsProvider.kt
new file mode 100644
index 00000000..b8398b94
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsProvider.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.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/dev/msfjarvis/aps/ui/settings/UserPreference.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt
deleted file mode 100644
index 315ebbda..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt
+++ /dev/null
@@ -1,674 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.settings
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.ShortcutManager
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.os.Environment
-import android.provider.DocumentsContract
-import android.provider.Settings
-import android.text.TextUtils
-import android.view.MenuItem
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
-import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree
-import androidx.annotation.RequiresApi
-import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.AppCompatTextView
-import androidx.core.content.edit
-import androidx.core.content.getSystemService
-import androidx.documentfile.provider.DocumentFile
-import androidx.preference.CheckBoxPreference
-import androidx.preference.EditTextPreference
-import androidx.preference.ListPreference
-import androidx.preference.Preference
-import androidx.preference.PreferenceFragmentCompat
-import androidx.preference.SwitchPreferenceCompat
-import com.github.ajalt.timberkt.Timber.tag
-import com.github.ajalt.timberkt.d
-import com.github.ajalt.timberkt.w
-import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel
-import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel
-import com.github.michaelbull.result.getOr
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dev.msfjarvis.aps.BuildConfig
-import dev.msfjarvis.aps.util.services.PasswordExportService
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.ui.crypto.BasePgpActivity
-import dev.msfjarvis.aps.ui.git.config.GitConfigActivity
-import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity
-import dev.msfjarvis.aps.util.git.sshj.SshKey
-import dev.msfjarvis.aps.util.pwgenxkpwd.XkpwdDictionary
-import dev.msfjarvis.aps.ui.sshkeygen.ShowSshKeyFragment
-import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity
-import dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity
-import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
-import dev.msfjarvis.aps.data.repo.PasswordRepository
-import dev.msfjarvis.aps.util.settings.PreferenceKeys
-import dev.msfjarvis.aps.util.extensions.autofillManager
-import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs
-import dev.msfjarvis.aps.util.extensions.getString
-import dev.msfjarvis.aps.util.extensions.sharedPrefs
-import java.io.File
-
-typealias ClickListener = Preference.OnPreferenceClickListener
-typealias ChangeListener = Preference.OnPreferenceChangeListener
-
-class UserPreference : AppCompatActivity() {
-
- private lateinit var prefsFragment: PrefsFragment
- private var fromIntent = false
-
- @Suppress("DEPRECATION")
- private val directorySelectAction = registerForActivityResult(OpenDocumentTree()) { uri: Uri? ->
- if (uri == null) return@registerForActivityResult
-
- tag(TAG).d { "Selected repository URI is $uri" }
- // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile
- val docId = DocumentsContract.getTreeDocumentId(uri)
- val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- val path = if (split.size > 1) split[1] else split[0]
- val repoPath = "${Environment.getExternalStorageDirectory()}/$path"
- val prefs = sharedPrefs
-
- tag(TAG).d { "Selected repository path is $repoPath" }
-
- if (Environment.getExternalStorageDirectory().path == repoPath) {
- MaterialAlertDialogBuilder(this)
- .setTitle(getString(R.string.sdcard_root_warning_title))
- .setMessage(getString(R.string.sdcard_root_warning_message))
- .setPositiveButton("Remove everything") { _, _ ->
- prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) }
- }
- .setNegativeButton(R.string.dialog_cancel, null)
- .show()
- }
- prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) }
- if (fromIntent) {
- setResult(RESULT_OK)
- finish()
- }
-
- }
-
- private val sshKeyImportAction = registerForActivityResult(OpenDocument()) { uri: Uri? ->
- if (uri == null) return@registerForActivityResult
- runCatching {
- SshKey.import(uri)
-
- Toast.makeText(this, resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show()
- setResult(RESULT_OK)
- finish()
- }.onFailure { e ->
- MaterialAlertDialogBuilder(this)
- .setTitle(resources.getString(R.string.ssh_key_error_dialog_title))
- .setMessage(e.message)
- .setPositiveButton(resources.getString(R.string.dialog_ok), null)
- .show()
- }
- }
-
- private val storeExportAction = registerForActivityResult(object : 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(applicationContext, uri)
-
- if (targetDirectory != null) {
- val service = Intent(applicationContext, PasswordExportService::class.java).apply {
- action = PasswordExportService.ACTION_EXPORT_PASSWORD
- putExtra("uri", uri)
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(service)
- } else {
- startService(service)
- }
- }
- }
-
- private val storeCustomXkpwdDictionaryAction = registerForActivityResult(OpenDocument()) { uri ->
- if (uri == null) return@registerForActivityResult
-
- Toast.makeText(
- this,
- this.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path),
- Toast.LENGTH_SHORT
- ).show()
-
- sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, uri.toString()) }
-
- val customDictPref = prefsFragment.findPreference(PreferenceKeys.PREF_KEY_CUSTOM_DICT)
- setCustomDictSummary(customDictPref, uri)
- // copy user selected file to internal storage
- val inputStream = contentResolver.openInputStream(uri)
- val customDictFile = File(filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE).outputStream()
- inputStream?.copyTo(customDictFile, 1024)
- inputStream?.close()
- customDictFile.close()
-
- setResult(RESULT_OK)
- }
-
- class PrefsFragment : PreferenceFragmentCompat() {
-
- private var autoFillEnablePreference: SwitchPreferenceCompat? = null
- private var clearSavedPassPreference: Preference? = null
- private var viewSshKeyPreference: Preference? = null
- private lateinit var oreoAutofillDependencies: List
- private lateinit var prefsActivity: UserPreference
- private lateinit var sharedPreferences: SharedPreferences
- private lateinit var encryptedPreferences: SharedPreferences
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- prefsActivity = requireActivity() as UserPreference
- val context = requireContext()
- sharedPreferences = preferenceManager.sharedPreferences
- encryptedPreferences = requireActivity().getEncryptedGitPrefs()
-
- addPreferencesFromResource(R.xml.preference)
-
- // Git preferences
- val gitServerPreference = findPreference(PreferenceKeys.GIT_SERVER_INFO)
- val openkeystoreIdPreference = findPreference(PreferenceKeys.SSH_OPENKEYSTORE_CLEAR_KEY_ID)
- val gitConfigPreference = findPreference(PreferenceKeys.GIT_CONFIG)
- val sshKeyPreference = findPreference(PreferenceKeys.SSH_KEY)
- val sshKeygenPreference = findPreference(PreferenceKeys.SSH_KEYGEN)
- viewSshKeyPreference = findPreference(PreferenceKeys.SSH_SEE_KEY)
- clearSavedPassPreference = findPreference(PreferenceKeys.CLEAR_SAVED_PASS)
- val deleteRepoPreference = findPreference(PreferenceKeys.GIT_DELETE_REPO)
- val externalGitRepositoryPreference = findPreference(PreferenceKeys.GIT_EXTERNAL)
- val selectExternalGitRepositoryPreference = findPreference(PreferenceKeys.PREF_SELECT_EXTERNAL)
-
- if (!PasswordRepository.isGitRepo()) {
- listOfNotNull(
- gitServerPreference,
- gitConfigPreference,
- sshKeyPreference,
- viewSshKeyPreference,
- clearSavedPassPreference,
- ).forEach {
- it.parent?.removePreference(it)
- }
- }
-
- // General preferences
- val showTimePreference = findPreference(PreferenceKeys.GENERAL_SHOW_TIME)
- val clearClipboard20xPreference = findPreference(PreferenceKeys.CLEAR_CLIPBOARD_20X)
-
- // Autofill preferences
- autoFillEnablePreference = findPreference(PreferenceKeys.AUTOFILL_ENABLE)
- val oreoAutofillDirectoryStructurePreference = findPreference(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE)
- val oreoAutofillDefaultUsername = findPreference(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME)
- val oreoAutofillCustomPublixSuffixes = findPreference(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES)
- oreoAutofillDependencies = listOfNotNull(
- oreoAutofillDirectoryStructurePreference,
- oreoAutofillDefaultUsername,
- oreoAutofillCustomPublixSuffixes,
- )
- oreoAutofillCustomPublixSuffixes?.apply {
- setOnBindEditTextListener {
- it.isSingleLine = false
- it.setHint(R.string.preference_custom_public_suffixes_hint)
- }
- }
-
- // Misc preferences
- val appVersionPreference = findPreference(PreferenceKeys.APP_VERSION)
-
- selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
- ?: getString(R.string.no_repo_selected)
- deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
- clearClipboard20xPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toInt() != 0
- openkeystoreIdPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty()
- ?: false
-
- updateAutofillSettings()
- updateClearSavedPassphrasePrefs()
-
- appVersionPreference?.summary = "Version: ${BuildConfig.VERSION_NAME}"
-
- sshKeyPreference?.onPreferenceClickListener = ClickListener {
- prefsActivity.getSshKey()
- true
- }
-
- sshKeygenPreference?.onPreferenceClickListener = ClickListener {
- prefsActivity.makeSshKey(true)
- true
- }
-
- viewSshKeyPreference?.onPreferenceClickListener = ClickListener {
- val df = ShowSshKeyFragment()
- df.show(parentFragmentManager, "public_key")
- true
- }
-
- clearSavedPassPreference?.onPreferenceClickListener = ClickListener {
- encryptedPreferences.edit {
- if (encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD) != null)
- remove(PreferenceKeys.HTTPS_PASSWORD)
- else if (encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) != null)
- remove(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
- }
- updateClearSavedPassphrasePrefs()
- true
- }
-
- openkeystoreIdPreference?.onPreferenceClickListener = ClickListener {
- sharedPreferences.edit { putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null) }
- it.isVisible = false
- true
- }
-
- gitServerPreference?.onPreferenceClickListener = ClickListener {
- startActivity(Intent(prefsActivity, GitServerConfigActivity::class.java))
- true
- }
-
- gitConfigPreference?.onPreferenceClickListener = ClickListener {
- startActivity(Intent(prefsActivity, GitConfigActivity::class.java))
- true
- }
-
- deleteRepoPreference?.onPreferenceClickListener = ClickListener {
- val repoDir = PasswordRepository.getRepositoryDirectory()
- MaterialAlertDialogBuilder(prefsActivity)
- .setTitle(R.string.pref_dialog_delete_title)
- .setMessage(resources.getString(R.string.dialog_delete_msg, repoDir))
- .setCancelable(false)
- .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
- runCatching {
- PasswordRepository.getRepositoryDirectory().deleteRecursively()
- PasswordRepository.closeRepository()
- }.onFailure {
- // TODO Handle the different cases of exceptions
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
- requireContext().getSystemService()?.apply {
- removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
- }
- }
- sharedPreferences.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) }
- dialogInterface.cancel()
- prefsActivity.finish()
- }
- .setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
- .show()
-
- true
- }
-
- selectExternalGitRepositoryPreference?.summary =
- sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
- ?: context.getString(R.string.no_repo_selected)
- selectExternalGitRepositoryPreference?.onPreferenceClickListener = ClickListener {
- prefsActivity.selectExternalGitRepository()
- true
- }
-
- val resetRepo = Preference.OnPreferenceChangeListener { _, o ->
- deleteRepoPreference?.isVisible = !(o as Boolean)
- PasswordRepository.closeRepository()
- sharedPreferences.edit { putBoolean(PreferenceKeys.REPO_CHANGED, true) }
- true
- }
-
- selectExternalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo
- externalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- autoFillEnablePreference?.onPreferenceClickListener = ClickListener {
- onEnableAutofillClick()
- true
- }
- }
-
- findPreference(PreferenceKeys.EXPORT_PASSWORDS)?.apply {
- isVisible = sharedPreferences.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- prefsActivity.exportPasswords()
- true
- }
- }
-
- showTimePreference?.onPreferenceChangeListener = ChangeListener { _, newValue: Any? ->
- runCatching {
- val isEnabled = newValue.toString().toInt() != 0
- clearClipboard20xPreference?.isVisible = isEnabled
- true
- }.getOr(false)
- }
-
- showTimePreference?.summaryProvider = Preference.SummaryProvider {
- getString(R.string.pref_clipboard_timeout_summary, sharedPreferences.getString
- (PreferenceKeys.GENERAL_SHOW_TIME, "45"))
- }
-
- findPreference(PreferenceKeys.ENABLE_DEBUG_LOGGING)?.isVisible = !BuildConfig.ENABLE_DEBUG_FEATURES
-
- findPreference(PreferenceKeys.BIOMETRIC_AUTH)?.apply {
- val canAuthenticate = BiometricAuthenticator.canAuthenticate(prefsActivity)
-
- if (!canAuthenticate) {
- isEnabled = false
- isChecked = false
- summary = getString(R.string.biometric_auth_summary_error)
- } else {
- setOnPreferenceClickListener {
- isEnabled = false
- sharedPreferences.edit {
- val checked = isChecked
- BiometricAuthenticator.authenticate(requireActivity()) { result ->
- when (result) {
- is BiometricAuthenticator.Result.Success -> {
- // Apply the changes
- putBoolean(PreferenceKeys.BIOMETRIC_AUTH, checked)
- isEnabled = true
- }
- else -> {
- // If any error occurs, revert back to the previous state. This
- // catch-all clause includes the cancellation case.
- putBoolean(PreferenceKeys.BIOMETRIC_AUTH, !checked)
- isChecked = !checked
- isEnabled = true
- }
- }
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
- requireContext().getSystemService()?.apply {
- removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
- }
- }
- }
- true
- }
- }
- }
-
- findPreference(PreferenceKeys.PROXY_SETTINGS)?.onPreferenceClickListener = ClickListener {
- startActivity(Intent(requireContext(), ProxySelectorActivity::class.java))
- true
- }
-
- val prefCustomXkpwdDictionary = findPreference(PreferenceKeys.PREF_KEY_CUSTOM_DICT)
- prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener {
- prefsActivity.storeCustomDictionaryPath()
- true
- }
- val dictUri = sharedPreferences.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT) ?: ""
-
- if (!TextUtils.isEmpty(dictUri)) {
- setCustomDictSummary(prefCustomXkpwdDictionary, Uri.parse(dictUri))
- }
-
- val prefIsCustomDict = findPreference(PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT)
- val prefCustomDictPicker = findPreference(PreferenceKeys.PREF_KEY_CUSTOM_DICT)
- val prefPwgenType = findPreference(PreferenceKeys.PREF_KEY_PWGEN_TYPE)
- updateXkPasswdPrefsVisibility(prefPwgenType?.value, prefIsCustomDict, prefCustomDictPicker)
-
- prefPwgenType?.onPreferenceChangeListener = ChangeListener { _, newValue ->
- updateXkPasswdPrefsVisibility(newValue, prefIsCustomDict, prefCustomDictPicker)
- true
- }
-
- prefIsCustomDict?.onPreferenceChangeListener = ChangeListener { _, newValue ->
- if (!(newValue as Boolean)) {
- val customDictFile = File(context.filesDir, XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE)
- if (customDictFile.exists() && !customDictFile.delete()) {
- w { "Failed to delete custom XkPassword dictionary: $customDictFile" }
- }
- prefCustomDictPicker?.setSummary(R.string.xkpwgen_pref_custom_dict_picker_summary)
- }
- true
- }
- }
-
- private fun updateXkPasswdPrefsVisibility(newValue: Any?, prefIsCustomDict: CheckBoxPreference?, prefCustomDictPicker: Preference?) {
- when (newValue as String) {
- BasePgpActivity.KEY_PWGEN_TYPE_CLASSIC -> {
- prefIsCustomDict?.isVisible = false
- prefCustomDictPicker?.isVisible = false
- }
- BasePgpActivity.KEY_PWGEN_TYPE_XKPASSWD -> {
- prefIsCustomDict?.isVisible = true
- prefCustomDictPicker?.isVisible = true
- }
- }
- }
-
- private fun updateAutofillSettings() {
- val isAutofillServiceEnabled = prefsActivity.isAutofillServiceEnabled
- val isAutofillSupported = prefsActivity.isAutofillServiceSupported
- if (!isAutofillSupported) {
- autoFillEnablePreference?.isVisible = false
- } else {
- autoFillEnablePreference?.isChecked = isAutofillServiceEnabled
- }
- oreoAutofillDependencies.forEach {
- it.isVisible = isAutofillServiceEnabled
- }
- }
-
- private fun updateClearSavedPassphrasePrefs() {
- clearSavedPassPreference?.apply {
- val sshPass = encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
- val httpsPass = encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD)
- if (sshPass == null && httpsPass == null) {
- isVisible = false
- return@apply
- }
- title = when {
- httpsPass != null -> getString(R.string.clear_saved_passphrase_https)
- sshPass != null -> getString(R.string.clear_saved_passphrase_ssh)
- else -> null
- }
- isVisible = true
- }
- }
-
- private fun updateViewSshPubkeyPref() {
- viewSshKeyPreference?.isVisible = SshKey.canShowSshPublicKey
- }
-
- @RequiresApi(Build.VERSION_CODES.O)
- private fun onEnableAutofillClick() {
- if (prefsActivity.isAutofillServiceEnabled) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- prefsActivity.autofillManager!!.disableAutofillServices()
- else
- throw IllegalStateException("isAutofillServiceEnabled == true, but Build.VERSION.SDK_INT < Build.VERSION_CODES.O")
- } else {
- MaterialAlertDialogBuilder(prefsActivity).run {
- setTitle(R.string.pref_autofill_enable_title)
- @SuppressLint("InflateParams")
- val layout =
- layoutInflater.inflate(R.layout.oreo_autofill_instructions, null)
- val supportedBrowsersTextView =
- layout.findViewById(R.id.supportedBrowsers)
- supportedBrowsersTextView.text =
- getInstalledBrowsersWithAutofillSupportLevel(context).joinToString(
- separator = "\n"
- ) {
- val appLabel = it.first
- val supportDescription = when (it.second) {
- BrowserAutofillSupportLevel.None -> getString(R.string.oreo_autofill_no_support)
- BrowserAutofillSupportLevel.FlakyFill -> getString(R.string.oreo_autofill_flaky_fill_support)
- BrowserAutofillSupportLevel.PasswordFill -> getString(R.string.oreo_autofill_password_fill_support)
- BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility -> getString(R.string.oreo_autofill_password_fill_and_conditional_save_support)
- BrowserAutofillSupportLevel.GeneralFill -> getString(R.string.oreo_autofill_general_fill_support)
- BrowserAutofillSupportLevel.GeneralFillAndSave -> 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 = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
- }
- startActivity(intent)
- }
- setNegativeButton(R.string.dialog_cancel, null)
- setOnDismissListener { updateAutofillSettings() }
- show()
- }
- }
- }
-
- override fun onResume() {
- super.onResume()
- updateAutofillSettings()
- updateClearSavedPassphrasePrefs()
- updateViewSshPubkeyPref()
- }
- }
-
- override fun onBackPressed() {
- super.onBackPressed()
- setResult(RESULT_OK)
- finish()
- }
-
- public override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- when (intent?.getStringExtra("operation")) {
- "get_ssh_key" -> getSshKey()
- "make_ssh_key" -> makeSshKey(false)
- "git_external" -> {
- fromIntent = true
- selectExternalGitRepository()
- }
- }
- prefsFragment = PrefsFragment()
-
- supportFragmentManager
- .beginTransaction()
- .replace(android.R.id.content, prefsFragment)
- .commit()
-
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- }
-
- @Suppress("Deprecation") // for Environment.getExternalStorageDirectory()
- fun selectExternalGitRepository() {
- MaterialAlertDialogBuilder(this)
- .setTitle(this.resources.getString(R.string.external_repository_dialog_title))
- .setMessage(this.resources.getString(R.string.external_repository_dialog_text))
- .setPositiveButton(R.string.dialog_ok) { _, _ ->
- directorySelectAction.launch(null)
- }
- .setNegativeButton(R.string.dialog_cancel, null)
- .show()
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- android.R.id.home -> {
- setResult(RESULT_OK)
- onBackPressed()
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- private fun importSshKey() {
- sshKeyImportAction.launch(arrayOf("*/*"))
- }
-
- /**
- * Opens a file explorer to import the private key
- */
- private fun getSshKey() {
- if (SshKey.exists) {
- MaterialAlertDialogBuilder(this).run {
- setTitle(R.string.ssh_keygen_existing_title)
- setMessage(R.string.ssh_keygen_existing_message)
- setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ ->
- importSshKey()
- }
- setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> }
- show()
- }
- } else {
- importSshKey()
- }
- }
-
- /**
- * Exports the passwords
- */
- private fun exportPasswords() {
- storeExportAction.launch(null)
- }
-
- /**
- * Opens a key generator to generate a public/private key pair
- */
- fun makeSshKey(fromPreferences: Boolean) {
- val intent = Intent(applicationContext, SshKeyGenActivity::class.java)
- startActivity(intent)
- if (!fromPreferences) {
- setResult(RESULT_OK)
- finish()
- }
- }
-
- /**
- * Pick custom xkpwd dictionary from sdcard
- */
- private fun storeCustomDictionaryPath() {
- storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*"))
- }
-
- private val isAutofillServiceSupported: Boolean
- get() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
- return autofillManager?.isAutofillSupported != null
- }
-
- private val isAutofillServiceEnabled: Boolean
- get() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
- return autofillManager?.hasEnabledAutofillServices() == true
- }
-
- companion object {
-
- private const val TAG = "UserPreference"
-
- fun createDirectorySelectionIntent(context: Context): Intent {
- return Intent(context, UserPreference::class.java).run {
- putExtra("operation", "git_external")
- }
- }
-
- /**
- * Set custom dictionary summary
- */
- @JvmStatic
- private fun setCustomDictSummary(customDictPref: Preference?, uri: Uri) {
- val fileName = uri.path?.substring(uri.path?.lastIndexOf(":")!! + 1)
- customDictPref?.summary = "Selected dictionary: $fileName"
- }
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt
index b260b77e..efcdd0f3 100644
--- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt
@@ -144,6 +144,7 @@ class SshKeyGenActivity : AppCompatActivity() {
.setTitle(getString(R.string.error_generate_ssh_key))
.setMessage(getString(R.string.ssh_key_error_dialog_text) + e.message)
.setPositiveButton(getString(R.string.dialog_ok)) { _, _ ->
+ setResult(RESULT_OK)
finish()
}
.show()
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt
new file mode 100644
index 00000000..1bb1056e
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.sshkeygen
+
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.git.sshj.SshKey
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import com.github.michaelbull.result.onFailure
+import com.github.michaelbull.result.runCatching
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+class SshKeyImportActivity : AppCompatActivity() {
+
+ private val sshKeyImportAction = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
+ if (uri == null) {
+ finish()
+ return@registerForActivityResult
+ }
+ runCatching {
+ SshKey.import(uri)
+ Toast.makeText(this, resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show()
+ setResult(RESULT_OK)
+ finish()
+ }.onFailure { e ->
+ MaterialAlertDialogBuilder(this)
+ .setTitle(resources.getString(R.string.ssh_key_error_dialog_title))
+ .setMessage(e.message)
+ .setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> finish() }
+ .show()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (SshKey.exists) {
+ MaterialAlertDialogBuilder(this).run {
+ setTitle(R.string.ssh_keygen_existing_title)
+ setMessage(R.string.ssh_keygen_existing_message)
+ setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ ->
+ importSshKey()
+ }
+ setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> finish() }
+ setOnCancelListener { finish() }
+ show()
+ }
+ } else {
+ importSshKey()
+ }
+ }
+
+ private fun importSshKey() {
+ sshKeyImportAction.launch(arrayOf("*/*"))
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt
index aa70bacb..bcc5aac1 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt
@@ -9,8 +9,10 @@ import android.os.Build
import androidx.annotation.RequiresApi
import com.github.androidpasswordstore.autofillparser.Credentials
import dev.msfjarvis.aps.data.password.PasswordEntry
+import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.services.getDefaultUsername
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
import java.io.File
import java.nio.file.Paths
@@ -110,8 +112,7 @@ enum class DirectoryStructure(val value: String) {
companion object {
- const val PREFERENCE = "oreo_autofill_directory_structure"
- private val DEFAULT = FileBased
+ val DEFAULT = FileBased
private val reverseMap = values().associateBy { it.value }
fun fromValue(value: String?) = if (value != null) reverseMap[value] ?: DEFAULT else DEFAULT
@@ -121,7 +122,7 @@ enum class DirectoryStructure(val value: String) {
object AutofillPreferences {
fun directoryStructure(context: Context): DirectoryStructure {
- val value = context.sharedPrefs.getString(DirectoryStructure.PREFERENCE, null)
+ val value = context.sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE)
return DirectoryStructure.fromValue(value)
}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt
index 44292fc6..83c25a7e 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt
@@ -15,7 +15,6 @@ import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.ui.settings.UserPreference
import dev.msfjarvis.aps.util.git.GitCommandExecutor
import dev.msfjarvis.aps.util.settings.AuthMode
import dev.msfjarvis.aps.util.settings.GitSettings
@@ -25,6 +24,8 @@ import dev.msfjarvis.aps.util.git.sshj.SshKey
import dev.msfjarvis.aps.util.git.sshj.SshjSessionFactory
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
import dev.msfjarvis.aps.data.repo.PasswordRepository
+import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity
+import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
@@ -92,9 +93,11 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
private fun getSshKey(make: Boolean) {
runCatching {
- // Ask the UserPreference to provide us with the ssh-key
- val intent = Intent(callingActivity.applicationContext, UserPreference::class.java)
- intent.putExtra("operation", if (make) "make_ssh_key" else "get_ssh_key")
+ val intent = if (make) {
+ Intent(callingActivity.applicationContext, SshKeyGenActivity::class.java)
+ } else {
+ Intent(callingActivity.applicationContext, SshKeyImportActivity::class.java)
+ }
callingActivity.startActivity(intent)
}.onFailure { e ->
e(e)
diff --git a/app/src/main/res/drawable/app_settings_alt_24px.xml b/app/src/main/res/drawable/app_settings_alt_24px.xml
new file mode 100644
index 00000000..a6dce94c
--- /dev/null
+++ b/app/src/main/res/drawable/app_settings_alt_24px.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_call_merge_24px.xml b/app/src/main/res/drawable/ic_call_merge_24px.xml
new file mode 100644
index 00000000..1ecd5dac
--- /dev/null
+++ b/app/src/main/res/drawable/ic_call_merge_24px.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_miscellaneous_services_24px.xml b/app/src/main/res/drawable/ic_miscellaneous_services_24px.xml
new file mode 100644
index 00000000..3fc185c5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_miscellaneous_services_24px.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wysiwyg_24px.xml b/app/src/main/res/drawable/ic_wysiwyg_24px.xml
new file mode 100644
index 00000000..7549868b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wysiwyg_24px.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_preference_recyclerview.xml b/app/src/main/res/layout/activity_preference_recyclerview.xml
new file mode 100644
index 00000000..fe8cfa8e
--- /dev/null
+++ b/app/src/main/res/layout/activity_preference_recyclerview.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index df28d86c..50de1a02 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -89,43 +89,42 @@
Als Klartext teilen
Zuletzt geändert %s
- Repository
- Git-Server Einstellungen
+ Repository
+ Git-Server Einstellungen
Lokale Git Konfiguration & Dienstprogramme
- Importiere SSH-Key
+ Importiere SSH-Key
Erstelle SSH-Schlüsselpaar
Zeige erstellten öffentlichen SSH-Key
- Repository löschen
+ Repository löschen
Repository löschen
Allgemein
- Passwörter
+ Passwörter
Timeout für das Kopieren des Passwortes
Legen Sie die Zeit (in Sekunden) fest, die das Passwort in der Zwischenablage liegen soll. 0 bedeutet für immer. Aktueller Wert: %1$s
Kopiere Passwort automatisch
- Kopiert das Passwort in die Zwischenablage, wenn der Eintrag entschlüsselt wurde.
+ Kopiert das Passwort in die Zwischenablage, wenn der Eintrag entschlüsselt wurde.
Die ausgewählte Datei scheint kein privater SSH-Schlüssel zu sein.
SSH-Key importiert
Schlüssel-Importfehler
Nachricht : \n
- Suche in Unterordnern
- Findet Passwörter auch in Unterordnern.
+ Suche in Unterordnern
+ Findet Passwörter auch in Unterordnern.
Passwortsortierung
Ordner zuerst
Dateien zuerst
Typ unabhängig
Zuletzt verwendet
- Automatisch ausfüllen
+ Automatisch ausfüllen
Autofill aktivieren
- Verschiedenes
+ Verschiedenes
Lösche die Zwischenablage 20-mal
- Speichert Nonsense 20-mal anstatt 1-mal in der Zwischenablage. Nützlich bspw. auf Samsung-Geräten, die den Verlauf der Zwischenablage speichern.
+ Speichert Nonsense 20-mal anstatt 1-mal in der Zwischenablage. Nützlich bspw. auf Samsung-Geräten, die den Verlauf der Zwischenablage speichern.
Lösche das lokale (versteckte) Repository
- Externes Repository
+ Externes Repository
Nutze ein externes Repository
- Wähle ein externes Repository
+ Wähle ein externes Repository
Passwörter exportieren
Exportiert die verschlüsselten Passwörter in ein externes Verzeichnis
- Version
Passwort generieren
Generieren
@@ -147,12 +146,12 @@
Eigene Wortliste: %1$s
Das ausgewählte Wörterbuch enthält nicht genügend Wörter der angegebenen Länge %1$d..%2$d
- Passwortgenerator
- Eigene Wortliste
- Eigene Wordlist-Datei verwenden
- Integrierte Wortliste verwenden
- Eigene Wortliste
- Tippen Sie, um eine benutzerdefinierte Wordlist-Datei mit einem Wort pro Zeile auszuwählen
+ Passwortgenerator
+ Eigene Wortliste
+ Eigene Wordlist-Datei verwenden
+ Integrierte Wortliste verwenden
+ Eigene Wortliste
+ Tippen Sie, um eine benutzerdefinierte Wordlist-Datei mit einem Wort pro Zeile auszuwählen
Passwort
Generieren
@@ -192,7 +191,7 @@
Soll weiterer Inhalt sichtbar sein?
Generieren
Aktualisieren
- Kein externes Repository ausgewählt
+ Kein externes Repository ausgewählt
Passwort unverschlüsselt senden an…
App Icon
@@ -238,10 +237,10 @@
Biometrische Abfrage
Authentifizierungsfehler
Authentifizierungsfehler: %s
- Biometrische Authentifizierung aktivieren
- Wenn aktiviert, werden Sie beim Starten der App nach Ihrem Fingerabdruck gefragt
- Fingerabdrucksensor fehlt oder ist nicht ansprechbar
- Lösche gespeicherte OpenKeystore SSH-Schlüssel-ID
+ Biometrische Authentifizierung aktivieren
+ Wenn aktiviert, werden Sie beim Starten der App nach Ihrem Fingerabdruck gefragt
+ Fingerabdrucksensor fehlt oder ist nicht ansprechbar
+ Lösche gespeicherte OpenKeystore SSH-Schlüssel-ID
Der Speicherort befindet sich in Ihrer SD-Karte oder im internen Speicher, aber die App hat nicht die Berechtigung, darauf zuzugreifen.
Ihr öffentlicher Schlüssel
Fehler beim Generieren des SSH-Schlüssels
@@ -254,16 +253,15 @@
Ziel muss innerhalb des repository sein
Ziel für %1$s angeben
Erstellen
- Suchfeld beim Start öffnen
- Suchleiste beim Start der App anzeigen
- Passwort Generator
+ Suchfeld beim Start öffnen
+ Suchleiste beim Start der App anzeigen
Hier tippen, um die Zwischenablage zu löschen
Das Repository muss geklont werden, bevor Änderungen synchronisert werden können.
- App Farbthema
- Hell
- Dunkel
- Durch Energiesparmodus gesetzt
- Systemstandard
+ App Farbthema
+ Hell
+ Dunkel
+ Durch Energiesparmodus gesetzt
+ Systemstandard
SSH-Schlüssel
Passwort
Konfiguration erfolgreich gespeichert
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index d53cac7e..73008ecf 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -91,43 +91,42 @@
Partager en clair
Dernière modification le %s
- Dépôt
- Modifier les paramètres du serveur Git
+ Dépôt
+ Modifier les paramètres du serveur Git
Configuration locale de Git & utilitaires
- Importer une clef SSH
+ Importer une clef SSH
Générer une paire de clefs SSH
Voir la clef publique SSH générée
- Supprimer le dépôt
+ Supprimer le dépôt
Effacer le dépôt
Général
- Mot de passe
+ Mot de passe
Délai imparti pour la copie
Définissez le temps (en secondes) durant lequel le mot de passe restera dans le presse-papiers. 0 pour une rétention illimitée. Valeur actuelle: %1$s
Copie automatique du mot de passe
- Copie automatiquement le mot de passe vers le presse-papier si le déchiffrement a réussi.
+ Copie automatiquement le mot de passe vers le presse-papier si le déchiffrement a réussi.
Le fichier sélectionné ne semble pas être une clé privée SSH.
Clef SSH importée
Erreur d\'importation de la clé
Message : \n
- Filtre récursif
- Cherche le mot de passe dans tous les sous-répertoires du répertoire actuel.
+ Filtre récursif
+ Cherche le mot de passe dans tous les sous-répertoires du répertoire actuel.
Ordre de tri des mots de passe
Dossiers en premier
Fichiers en premier
Indifférent au type d\'entrée
Récemment utilisé
- Saisie automatique
+ Saisie automatique
Saisie automatique
- Divers
+ Divers
Effacer le presse-papier 20 fois
- Enregistre des informations absurdes dans le presse-papier 20 fois à la place d\'une seule. Utile sur les téléphones Samsung qui disposent d\'un historique du presse-papier.
+ Enregistre des informations absurdes dans le presse-papier 20 fois à la place d\'une seule. Utile sur les téléphones Samsung qui disposent d\'un historique du presse-papier.
Supprime le dépot local (caché)
- Dépôt externe
+ Dépôt externe
Utilise un dépôt externe pour les mots de passe
- Choisissez un dépôt externe
+ Choisissez un dépôt externe
Exporter les mots de passe
Exporter les mots de passe (chiffrés) vers un répertoire externe
- Version
Générer un mot de passe
Générer
@@ -149,12 +148,12 @@
Liste de mots personnalisée : %1$s
Le dictionnaire sélectionné ne contient pas assez de mots de la longueur %1$d..%2$d
- Type de générateur de mot de passe
- Liste de mots personnalisée
- Utiliser une liste de mots personnalisée
- Utilisation de la liste de mots intégrée
- Fichier personnalisé de liste de mots
- Touchez pour choisir un fichier de liste de mots personnalisés contenant un mot par ligne
+ Type de générateur de mot de passe
+ Liste de mots personnalisée
+ Utiliser une liste de mots personnalisée
+ Utilisation de la liste de mots intégrée
+ Fichier personnalisé de liste de mots
+ Touchez pour choisir un fichier de liste de mots personnalisés contenant un mot par ligne
Mot de passe
Générer
@@ -191,7 +190,7 @@
Controller la visibilité du contenu supplémentaire une fois déchiffré
Générer
Rafraichir la liste
- Pas de dépôt externe séléctionné
+ Pas de dépôt externe séléctionné
Envoyer le mot de passe en clair via…
Icône de l\'application
@@ -224,9 +223,9 @@
Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)
Réinitialisation dure de la branche distante
Identification biométrique
- Authentification biométrique
- Lorsque cette option est activée, Password Store vous demandera votre empreinte digitale au lancement
- Lecteur d\'empreinte digitale non accessible ou manquant
+ Authentification biométrique
+ Lorsque cette option est activée, Password Store vous demandera votre empreinte digitale au lancement
+ Lecteur d\'empreinte digitale non accessible ou manquant
Votre clé publique
Une erreur est survenue pendant la génération de la clé ssh
Afficher tous les fichiers et dossiers
@@ -235,14 +234,13 @@
Renommer le dossier
Entrez le chemin pour %1$s
Créer
- Ouvrir la recherche au démarrage
- Ouvrir la barre de recherche au démarrage de l\'application
- Générateur de mot de passe
+ Ouvrir la recherche au démarrage
+ Ouvrir la barre de recherche au démarrage de l\'application
Appuyez ici pour effacer le presse-papiers
- Thème de l\'application
- Clair
- Sombre
- Thème système
+ Thème de l\'application
+ Clair
+ Sombre
+ Thème système
Clé SSH
Mot de passe
L\'URL du dépôt fournie n\'est pas valide
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 89b4da27..5de9af33 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -93,43 +93,42 @@
Compartir como texto plano
Último cambio %s
- Repositorio
- Editar axustes do servidor git
+ Repositorio
+ Editar axustes do servidor git
Utilidades Git
- Importar chave SSH
+ Importar chave SSH
Crear par de chaves SSH
Ver a chave pública SSH creada
- Eliminar repositorio
+ Eliminar repositorio
Baleirar repositorio
Xeral
- Contrasinais
+ Contrasinais
Caducidade do copiado do contrasinal
Establece os segundos que queres que o contrasinal permaneza copiado no portapapeis. 0 significa para sempre. Valor actual: %1$s
Copiar contrasinal automáticamente
- Copia automáticamente o contrasinal ao portapapeis se o descifra correctamente
+ Copia automáticamente o contrasinal ao portapapeis se o descifra correctamente
O ficheiro escollido non semella ser unha chave privada SSH.
Chave-SSH importada
Houbo un fallo ao importar a chave ssh
Mensaxe : \n
- Filtro recursivo
- Atopa as chaves de xeito recursivo no directorio actual.
+ Filtro recursivo
+ Atopa as chaves de xeito recursivo no directorio actual.
Orde para mostrar contrasinais
Primeiro cartafoles
Primeiro ficheiros
Independentemente do tipo
Usadas recentemente
- Completado automático
+ Completado automático
Activar completado automático
- Varios
+ Varios
Baleirar portapapeis 20 veces
- Gardar números consecutivos no portapapeis 20 veces. Resulta útil nos móbiles Samsung que gardan historial no portapapeis.
+ Gardar números consecutivos no portapapeis 20 veces. Resulta útil nos móbiles Samsung que gardan historial no portapapeis.
Elimina repositorio local (oculto).
- Repositorio externo
+ Repositorio externo
Usar un repositorio externo de contrasinais
- Selecciona repositorio externo
+ Selecciona repositorio externo
Exportar contrasinais
Exporta os contrasinais cifrados a un directorio externo
- Versión
Crear contrasinal
Crear
@@ -151,12 +150,12 @@
Lista persoal de palabras: %1$s
O dicionario non contén palabras suficientes da lonxitude dada %1$d .. %2$d
- Tipo de creador de contrasinais
- Lista persoal de palabras
- Usar ficheiro con palabras personalizadas
- Usar lista de palabras incluída
- Ficheiro persoal de palabras
- Toca para escoller un ficheiro persoal con palabras que conteña unha palabra por liña
+ Tipo de creador de contrasinais
+ Lista persoal de palabras
+ Usar ficheiro con palabras personalizadas
+ Usar lista de palabras incluída
+ Ficheiro persoal de palabras
+ Toca para escoller un ficheiro persoal con palabras que conteña unha palabra por liña
Frase de paso
Crear
@@ -196,7 +195,7 @@
Controla a visibilidade do contido extra unha vez descifrado
Crear
Actualizar lista
- Non hai seleccionado ningún repositorio externo
+ Non hai seleccionado ningún repositorio externo
Enviar contrasinal como texto plano usando...
Icona da app
@@ -249,10 +248,10 @@ a app desde unha fonte de confianza, como a Play Store, Amazon Appstore, F-Droid
Petición biométrica
Fallo de autenticación
Fallou a autenticación: %s
- Activar autenticación biométrica
- Ao activala, Password Store vaiche pedir a túa pegada dactilar ao iniciar a app
- O hardware de pegada dixital non é accesible ou existente
- Eliminar o ID lembrado da chave SSH en OpenKeystore
+ Activar autenticación biométrica
+ Ao activala, Password Store vaiche pedir a túa pegada dactilar ao iniciar a app
+ O hardware de pegada dixital non é accesible ou existente
+ Eliminar o ID lembrado da chave SSH en OpenKeystore
O almacenaxe está na tarxeta SD pero a app non ten permiso para acceder a el. Por favor concédelle permiso.
A túa chave pública
Algo fallou ao intentar crear a chave-ssh
@@ -265,16 +264,15 @@ a app desde unha fonte de confianza, como a Play Store, Amazon Appstore, F-Droid
O destino debe estar dentro do repositorio
Escribe o destino para %1$s
Crear
- Abrir busca ao inicio
- Abrir a barra de busca cando se inicia a app
- Creador de contrasinais
+ Abrir busca ao inicio
+ Abrir a barra de busca cando se inicia a app
Toca aquí para baleirar o portapapeis
Clonar un repositorio git para sincronizar os cambios
- Decorado da App
- Claro
- Escuro
- Establecido polo Aforrador de enerxía
- Por omisión do sistema
+ Decorado da App
+ Claro
+ Escuro
+ Establecido polo Aforrador de enerxía
+ Por omisión do sistema
Chave SSH
Contrasinal
Configuración gardada
@@ -354,7 +352,7 @@ a app desde unha fonte de confianza, como a Play Store, Amazon Appstore, F-Droid
Servidor proxy
Porto
- Axustes do proxy HTTP(S)
+ Axustes do proxy HTTP(S)
URL non válido
Completa e garda contrasinais (gardar require que os servizos de accesibilidade non estén activados)
Eliminar chave do host gardada
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 5fcd812d..103283ca 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -93,43 +93,42 @@
Condividi come testo semplice
Ultima modifica %s
- Repository
- Modifica impostazioni del server di Git
+ Repository
+ Modifica impostazioni del server di Git
Configurazione & utilità della configurazione di Git
- Importa chiave SSH
+ Importa chiave SSH
Genera coppia di chiavi SSH
Visualizza la chiave SSH pubblica generata
- Elimina repository
+ Elimina repository
Cancella repository
Generale
- Password
+ Password
Timeout copia della password
Imposta per quanto tempo (in secondi) vuoi che la password rimanga negli appunti. 0 significa per sempre. Valore corrente: %1$s
Copia automaticamente la password
- Copia automaticamente la password negli appunti dopo il successo della decifratura.
+ Copia automaticamente la password negli appunti dopo il successo della decifratura.
Il file selezionato non sembra essere una chiave privata SSH.
Chiave SSH importata
Errore di importazione della chiave
Messaggio : \n
- Filtro ricorsivo
- Trova ricorsivamente le password della directory corrente.
+ Filtro ricorsivo
+ Trova ricorsivamente le password della directory corrente.
Ordine password
Prima le cartelle
Prima i file
Tipo indipendente
Usato di recente
- Auto-compilazione
+ Auto-compilazione
Abilita Auto-Compilazione
- Varie
+ Varie
Cancella 20 volte gli appunti
- Archivia i numeri consecutivi negli appunti per 20 volte. Utile sui telefoni Samsung che presentano la cronologia degli appunti.
+ Archivia i numeri consecutivi negli appunti per 20 volte. Utile sui telefoni Samsung che presentano la cronologia degli appunti.
Elimina repository locale (nascosta)
- Repository Esterna
+ Repository Esterna
Usa una repository di password esterna
- Seleziona repository esterna
+ Seleziona repository esterna
Esporta password
Esporta le password crittografate ad una directory esterna
- Versione
Genera Password
Genera
@@ -151,12 +150,12 @@
Lista di parole personalizzata: %1$s
Il dizionario selezionato non contiene abbastanza parole della data lunghezza %1$d..%2$d
- Tipo di generatore di password
- Lista di parole personalizzata
- Usando file di elenco di parole personalizzati
- Usando liste di parole integrate
- File di elenco di parole personalizzato
- Tocca per selezionare un file di lista di parole personalizzato contenente una parola per riga
+ Tipo di generatore di password
+ Lista di parole personalizzata
+ Usando file di elenco di parole personalizzati
+ Usando liste di parole integrate
+ File di elenco di parole personalizzato
+ Tocca per selezionare un file di lista di parole personalizzato contenente una parola per riga
Frase Segreta
Genera
@@ -196,7 +195,7 @@
Controlla la visibilità del contenuto extra una volta decrittografato.
Genera
Aggiorna elenco
- Nessuna repository esterna selezionata
+ Nessuna repository esterna selezionata
Invia password come testo semplice usando…
Icona app
@@ -248,10 +247,10 @@
Richiesta Biometrica
Autenticazione non riuscita
Autenticazione non riuscita: %s
- Abilita autenticazione biometrica
- Quando abilitata, il Password Store ti chiederà la tua impronta digitale al lancio dell\'app
- L\'hardware delle impronte digitali non è accessibile o mancante
- Elimina l\'ID della Chiave SSH di OpenKeystore memorizzato
+ Abilita autenticazione biometrica
+ Quando abilitata, il Password Store ti chiederà la tua impronta digitale al lancio dell\'app
+ L\'hardware delle impronte digitali non è accessibile o mancante
+ Elimina l\'ID della Chiave SSH di OpenKeystore memorizzato
La posizione dell\'archiviazione nella tua Scheda SD o Archiviazione Interna, ma l\'app non ha i permessi per accedervi.
La tua chiave pubblica
Errore provando a generare la chiave-ssh
@@ -264,16 +263,15 @@
La destinazione deve essere nella repository
Inserire destinazione per %1$s
Crea
- Apri ricerca all\'avvio
- Apri la barra di ricerca al lancio dell\'app
- Generatore di Password
+ Apri ricerca all\'avvio
+ Apri la barra di ricerca al lancio dell\'app
Tocca qui per cancellare gli appunti
La repository deve essere clonata prima di sincronizzare le modifiche.
- Tema dell\'App
- Chiaro
- Scuro
- Impostato dal Risparmio Energetico
- Predefinito del sistema
+ Tema dell\'App
+ Chiaro
+ Scuro
+ Impostato dal Risparmio Energetico
+ Predefinito del sistema
Chiave SSH
Password
Configurazione correttamente salvata
@@ -353,7 +351,7 @@
Nome host del proxy
Porta
- Impostazioni proxy HTTP(S)
+ Impostazioni proxy HTTP(S)
URL non valido
Compila e salva le password (il salvataggio necessita che nessun servizio di accessibilità sia abilitato)
Cancella la chiave host salvata
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index a26ca996..ff6b5fc5 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -93,43 +93,42 @@
Compartilhar como texto
Última alteração %s
- Repositório
- Editar configurações do servidor Git
+ Repositório
+ Editar configurações do servidor Git
Configuração local do Git & utilitários
- Importar chave SSH
+ Importar chave SSH
Gerar um par de chave SSH
Ver a chave SSH pública gerada
- Excluir repositório
+ Excluir repositório
Limpar repositório
Geral
- Senhas
+ Senhas
Tempo limite de cópia da senha
Definir o tempo (em segundos) que a senha está na área de transferência. 0 significa para sempre. Valor atual: %1$s
Copiar senha automaticamente
- Automaticamente copie a senha para a área de transferência após a descriptografia ser bem sucedida.
+ Automaticamente copie a senha para a área de transferência após a descriptografia ser bem sucedida.
O arquivo selecionado não parece ser uma chave SSH privada.
Chave SSH importada
Erro ao importar chave
Mensagem : \n
- Filtragem recursiva
- Encontrar senhas do diretório corrente recursivamente.
+ Filtragem recursiva
+ Encontrar senhas do diretório corrente recursivamente.
Ordenação da Senha
Pastas primeiro
Arquivos primeiro
Tipo independente
Usado recentemente
- Preenchimento Automático
+ Preenchimento Automático
Ativar preenchimento automático
- Outros
+ Outros
Limpar área de transferência 20 vezes
- Armazene números consecutivos na área de transferência 20 vezes. Útil em telefones Samsung que apresentam o histórico da área de transferência.
+ Armazene números consecutivos na área de transferência 20 vezes. Útil em telefones Samsung que apresentam o histórico da área de transferência.
Exclui o repositório local (oculto)
- Repositório externo
+ Repositório externo
Use um repositório de senha externo
- Selecionar repositório externo
+ Selecionar repositório externo
Exportar senhas
Exporta as senhas criptografadas para um diretório externo
- Versão
Gerar Senha
Gerar
@@ -151,12 +150,12 @@
Lista de palavras personalizada: %1$s
O dicionário selecionado não contém palavras suficientes de tamanho %1$d..%2$d
- Tipo de gerador de senha
- Lista de palavras personalizadas
- Usando um arquivo de Lista de Palavras
- Usando o arquivo de palavras embutido
- Lista de palavras personalizadas
- Toque para escolher um arquivo personalizado de lista de palavras contendo uma palavra por linha
+ Tipo de gerador de senha
+ Lista de palavras personalizadas
+ Usando um arquivo de Lista de Palavras
+ Usando o arquivo de palavras embutido
+ Lista de palavras personalizadas
+ Toque para escolher um arquivo personalizado de lista de palavras contendo uma palavra por linha
Frase Secreta
Gerar
@@ -196,7 +195,7 @@
Controlar a visibilidade do conteúdo extra uma vez descriptografado.
Gerar
Atualizar lista
- Nenhum repositório externo selecionado
+ Nenhum repositório externo selecionado
Enviar senha como texto simples usando…
Ícone do aplicativo
@@ -247,10 +246,10 @@
Confirmação Biométrica
Falha de autenticação
Falha de autenticação: %s
- Ativar autenticação biométrica
- Quando ativado, o Password Store irá pedir a sua impressão digital ao iniciar o aplicativo
- Hardware de impressão digital não acessível ou ausente
- Limpar ID de chave SSH lembrada do OpenKeystore
+ Ativar autenticação biométrica
+ Quando ativado, o Password Store irá pedir a sua impressão digital ao iniciar o aplicativo
+ Hardware de impressão digital não acessível ou ausente
+ Limpar ID de chave SSH lembrada do OpenKeystore
O local do armazenamento está em seu cartão SD ou armazenamento interno, mas o aplicativo não tem permissão para acessá-lo.
Sua chave pública
Erro ao tentar gerar a chave SSH
@@ -263,16 +262,15 @@
O destino deve estar dentro do repositório
Insira o destino para %1$s
Criar
- Abrir pesquisa ao inicializar
- Abrir barra de pesquisa quando o aplicativo for iniciado
- Gerador de Senha
+ Abrir pesquisa ao inicializar
+ Abrir barra de pesquisa quando o aplicativo for iniciado
Toque aqui para limpar a área de transferência
O repositório deve ser clonado antes de sincronizar as alterações.
- Tema do aplicativo
- Claro
- Escuro
- Configurado pela Economia de bateria
- Padrão do sistema
+ Tema do aplicativo
+ Claro
+ Escuro
+ Configurado pela Economia de bateria
+ Padrão do sistema
Chave SSH
Senha
Configuração salva com sucesso
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index e8ca9488..f0e3c6d4 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -97,43 +97,42 @@
Поделиться в открытом виде
Последние изменение %s
- Репозиторий
- Изменить настройки сервера Git
+ Репозиторий
+ Изменить настройки сервера Git
Конфигурация локального Git
- Импортировать SSH ключ
+ Импортировать SSH ключ
Сгенерировать пару SSH ключей
Просмотреть публичный SSH ключ
- Удалить репозиторий
+ Удалить репозиторий
Очистить репозиторий
Общие
- Пароли
+ Пароли
Срок хранения пароля в буфере обмена
Установите время (в секундах), которое вы хотите, чтобы пароль был в буфере обмена. 0 означает навсегда. Текущее значение: %1$s
Автоматически копировать пароль
- Автоматически копировать пароль в буфер обмена после успешного расшифрования
+ Автоматически копировать пароль в буфер обмена после успешного расшифрования
Выбранный файл не похож на приватный SSH-ключ.
SSH ключ импортирован
Ошибка импорта ключа
Сообщение: \n
- Рекурсивная фильтрация
- Рекурсивный поиск паролей в текущей директории
+ Рекурсивная фильтрация
+ Рекурсивный поиск паролей в текущей директории
Порядок сортировки паролей
Сначала папки
Сначала файлы
Типонезависимый
Недавно использованные
- Автозаполнение
+ Автозаполнение
Включить автозаполнение
- Другое
+ Другое
Очистить буфер 20 раз
- Вставлять чепуху в буфер обмена 20 раз вместо одного. Полезно на некоторых моделях телефонов с запоминанием истории буфера обмена
+ Вставлять чепуху в буфер обмена 20 раз вместо одного. Полезно на некоторых моделях телефонов с запоминанием истории буфера обмена
Удалить локальный (скрытый) репозиторий
- Внешний репозиторий
+ Внешний репозиторий
Использовать внешний репозиторий
- Выбрать внешний репозиторий
+ Выбрать внешний репозиторий
Экспортировать пароли
Экспортировать пароли в открытом виде во внешнее хранилище
- Версия
Сгенерировать пароль
Сгенерировать
@@ -155,12 +154,12 @@
Пользовательский список слов: %1$s
Выбранный словарь не содержит достаточного количества слова заданной длинны %1$d..%2$d
- Тип генератора паролей
- Пользовательский список слов
- Использовать файл списка слов созданный пользователем
- Использовать встроенный список слов
- Файл пользовательского списка слов
- Нажмите чтобы выбрать файл пользовательского списка слов содержащий одно слово на строку
+ Тип генератора паролей
+ Пользовательский список слов
+ Использовать файл списка слов созданный пользователем
+ Использовать встроенный список слов
+ Файл пользовательского списка слов
+ Нажмите чтобы выбрать файл пользовательского списка слов содержащий одно слово на строку
Пароль
Сгенерировать
@@ -200,7 +199,7 @@
Видимость поля дополнительной информации после расшифрования
Сгенерировать
Обновить список
- Внешний репозиторий не выбран
+ Внешний репозиторий не выбран
Поделиться паролем в открытом виде с помощью
Иконка приложения
@@ -251,10 +250,10 @@
Запрос биометрии
Ошибка авторизации
Ошибка аутентификации: %s
- Включить биометрическую аутентификацию
- Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения
- Сенсор отпечатка пальца не доступен или отсутствует
- Очистить сохраненный SSH Key идентификатор OpenKystortore
+ Включить биометрическую аутентификацию
+ Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения
+ Сенсор отпечатка пальца не доступен или отсутствует
+ Очистить сохраненный SSH Key идентификатор OpenKystortore
Хранилище расположено на вашей SD-карте или во внутреннем хранилище, но у приложения нет разрешения на доступ к нему.
Ваш публичный ключ
Возникла ошибка при попытке генерации ssh ключа
@@ -267,16 +266,15 @@
Путь должен указывать на область внутри репозитория
Введите адрес назначения для %1$s
Создать
- Открыть поиск на старте
- Открыть панель поиска при запуске приложения
- Генератор паролей
+ Открыть поиск на старте
+ Открыть панель поиска при запуске приложения
Нажмите здесь чтобы очистить буфер обмена
Для синхронизации изменений клонируйте git репозиторий
- Тема приложения
- Светлая
- Темная
- Задается экономией батареи
- Системная
+ Тема приложения
+ Светлая
+ Темная
+ Задается экономией батареи
+ Системная
SSH ключ
Пароль
Конфигурация успешно сохранена
diff --git a/app/src/main/res/values-v29/arrays.xml b/app/src/main/res/values-v29/arrays.xml
index 97443b9f..13dfa87e 100644
--- a/app/src/main/res/values-v29/arrays.xml
+++ b/app/src/main/res/values-v29/arrays.xml
@@ -5,9 +5,9 @@
- - @string/theme_light
- - @string/theme_dark
- - @string/theme_follow_system
+ - @string/pref_app_theme_value_light
+ - @string/pref_app_theme_value_dark
+ - @string/pref_app_theme_value_follow_system
- light
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 2b320f31..a55e37b2 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -41,9 +41,9 @@
- directory
- - @string/theme_light
- - @string/theme_dark
- - @string/theme_battery_saver
+ - @string/pref_app_theme_value_light
+ - @string/pref_app_theme_value_dark
+ - @string/pref_app_theme_value_battery_saver
- light
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a370d230..46e85334 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -110,43 +110,43 @@
Last changed %s
- Repository
- Edit Git server settings
+ Repository
+ Edit Git server settings
Local Git config & utilities
- Import SSH key
+ Import SSH key
Generate SSH key pair
View generated public SSH key
- Delete repository
+ Delete repository
Clear repository
General
- Passwords
+ Passwords
Password copy timeout
Set the time (in seconds) you want the password to be in clipboard. 0 means forever. Current value: %1$s
Automatically copy password
- Automatically copy the password to the clipboard after decryption was successful.
+ Automatically copy the password to the clipboard after decryption was successful.
Selected file does not appear to be an SSH private key.
SSH-key imported
Key import error
Message : \n
- Recursive filtering
- Recursively find passwords of the current directory.
+ Recursive filtering
+ Recursively find passwords of the current directory.
Password sort order
Folders first
Files first
Type independent
Recently used
- Autofill
+ Autofill
Enable Autofill
- Misc
+ Misc
Clear clipboard 20 times
- Store consecutive numbers in the clipboard 20 times. Useful on Samsung phones that feature clipboard history.
+ Store consecutive numbers in the clipboard 20 times. Useful on Samsung phones that feature clipboard history.
Deletes local (hidden) repository
- External repository
+ External repository
Use an external password repository
- Select external repository
+ Select external repository
+ No external repository selected
Export passwords
Exports the encrypted passwords to an external directory
- Version
Generate Password
@@ -171,12 +171,12 @@
Selected dictionary does not contain enough words of given length %1$d..%2$d
- Password generator type
- Custom wordlist
- Using custom wordlist file
- Using built-in wordlist
- Custom worldlist file
- Tap to pick a custom wordlist file containing one word per line
+ Password generator type
+ Custom wordlist
+ Using custom wordlist file
+ Using built-in wordlist
+ Custom worldlist file
+ Tap to pick a custom wordlist file containing one word per line
Passphrase
@@ -220,7 +220,6 @@
Control the visibility of the extra content once decrypted.
Generate
Refresh list
- No external repository selected
Send password as plaintext using…
App icon
@@ -271,14 +270,15 @@
Unable to locate HEAD
SD-Card root selected
You have selected the root of your sdcard for the store. This is extremely dangerous and you will lose your data as its content will, eventually, be deleted
+ Remove everything
Abort and Push
Biometric Prompt
Authentication failure
Authentication failure: %s
- Enable biometric authentication
- When enabled, Password Store will prompt you for your fingerprint when launching the app
- Fingerprint hardware not accessible or missing
- Clear remembered OpenKeystore SSH Key ID
+ Enable biometric authentication
+ When enabled, Password Store will prompt you for your fingerprint when launching the app
+ Fingerprint hardware not accessible or missing
+ Clear remembered OpenKeystore SSH Key ID
The store location is in your SD Card or Internal storage, but the app does not have permission to access it.
Your public key
Error while trying to generate the ssh-key
@@ -291,16 +291,15 @@
Destination must be within the repository
Enter destination for %1$s
Create
- Open search on start
- Open search bar when app is launched
- Password Generator
+ Open search on start
+ Open search bar when app is launched
Tap here to clear clipboard
The repository must be cloned before syncing changes.
- App theme
- Light
- Dark
- Set by Battery Saver
- System default
+ App theme
+ Light
+ Dark
+ Set by Battery Saver
+ System default
SSH key
Password
OpenKeychain
@@ -389,7 +388,7 @@
Proxy hostname
Port
- HTTP(S) proxy settings
+ HTTP(S) proxy settings
Invalid URL
Fill and save passwords (saving requires that no accessibility services are enabled)
Clear saved host key
diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml
deleted file mode 100644
index c0b3a105..00000000
--- a/app/src/main/res/xml/preference.xml
+++ /dev/null
@@ -1,174 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 32abf6ab..8cffd7c2 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -53,6 +53,7 @@ object Dependencies {
const val jgit = "org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r"
const val kotlin_result = "com.michael-bull.kotlin-result:kotlin-result:1.1.9"
const val leakcanary = "com.squareup.leakcanary:leakcanary-android:2.5"
+ const val modern_android_prefs = "de.Maxr1998.android:modernpreferences:1.2.0-alpha1"
const val plumber = "com.squareup.leakcanary:plumber-android:2.5"
const val sshj = "com.hierynomus:sshj:0.30.0"
const val ssh_auth = "org.sufficientlysecure:sshauthentication-api:1.0"