diff options
Diffstat (limited to 'app/src')
35 files changed, 0 insertions, 1997 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0429b2ac..546bdde4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,11 +34,6 @@ tools:targetApi="s"> <activity - android:name=".ui.onboarding.activity.OnboardingActivity" - android:configChanges="orientation|screenSize" - android:exported="false" /> - - <activity android:name=".ui.proxy.ProxySelectorActivity" android:exported="false" android:label="@string/activity_proxy_label" @@ -73,12 +68,6 @@ tools:node="replace" /> <activity - android:name=".ui.git.config.GitServerConfigActivity" - android:exported="false" - android:label="@string/title_activity_git_clone" - android:windowSoftInputMode="adjustResize" /> - - <activity android:name=".ui.git.config.GitConfigActivity" android:exported="false" android:label="@string/title_activity_git_config" @@ -118,16 +107,6 @@ </service> <activity - android:name=".ui.sshkeygen.SshKeyImportActivity" - android:exported="false" - android:theme="@style/NoBackgroundThemeM3" - android:windowSoftInputMode="adjustResize" /> - <activity - android:name=".ui.sshkeygen.SshKeyGenActivity" - android:exported="false" - android:label="@string/pref_ssh_keygen_title" - android:windowSoftInputMode="adjustResize" /> - <activity android:name=".ui.autofill.AutofillDecryptActivity" android:exported="false" android:theme="@style/NoBackgroundThemeM3" /> @@ -150,14 +129,6 @@ android:exported="false" android:theme="@style/DialogLikeThemeM3" android:windowSoftInputMode="adjustNothing" /> - <activity - android:name=".ui.pgp.PGPKeyImportActivity" - android:theme="@style/NoBackgroundThemeM3" /> - <activity - android:name=".ui.pgp.PGPKeyListActivity" - android:exported="false" - android:label="@string/activity_label_pgp_key_manager" - android:parentActivityName=".ui.settings.SettingsActivity" /> </application> <queries> diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/BasicBottomSheet.kt b/app/src/main/java/app/passwordstore/ui/dialogs/BasicBottomSheet.kt deleted file mode 100644 index e689f3bd..00000000 --- a/app/src/main/java/app/passwordstore/ui/dialogs/BasicBottomSheet.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.dialogs - -import android.content.Context -import android.content.DialogInterface.OnDismissListener -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewTreeObserver -import android.widget.FrameLayout -import androidx.annotation.StringRes -import androidx.core.view.isVisible -import app.passwordstore.R -import app.passwordstore.databinding.BasicBottomSheetBinding -import app.passwordstore.util.extensions.viewBinding -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment - -/** - * [BottomSheetDialogFragment] that exposes a simple [androidx.appcompat.app.AlertDialog] like API - * through [BasicBottomSheet.Builder] to create a similar UI, just at the bottom of the screen. - */ -class BasicBottomSheet -private constructor( - val title: String?, - val message: String, - val positiveButtonLabel: String?, - val negativeButtonLabel: String?, - val positiveButtonClickListener: View.OnClickListener?, - val negativeButtonClickListener: View.OnClickListener?, - val onDismissListener: OnDismissListener?, -) : BottomSheetDialogFragment() { - - private val binding by viewBinding(BasicBottomSheetBinding::bind) - - private var behavior: BottomSheetBehavior<FrameLayout>? = null - private val bottomSheetCallback = - object : BottomSheetBehavior.BottomSheetCallback() { - override fun onSlide(bottomSheet: View, slideOffset: Float) {} - - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == BottomSheetBehavior.STATE_COLLAPSED) { - dismiss() - } - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - if (savedInstanceState != null) dismiss() - return layoutInflater.inflate(R.layout.basic_bottom_sheet, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - view.viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - view.viewTreeObserver.removeOnGlobalLayoutListener(this) - val dialog = dialog as BottomSheetDialog? ?: return - behavior = dialog.behavior - behavior?.apply { - state = BottomSheetBehavior.STATE_EXPANDED - peekHeight = 0 - addBottomSheetCallback(bottomSheetCallback) - } - if (!title.isNullOrEmpty()) { - binding.bottomSheetTitle.isVisible = true - binding.bottomSheetTitle.text = title - } - binding.bottomSheetMessage.text = message - if (positiveButtonClickListener != null) { - positiveButtonLabel?.let { buttonLbl -> binding.bottomSheetOkButton.text = buttonLbl } - binding.bottomSheetOkButton.isVisible = true - binding.bottomSheetOkButton.setOnClickListener { - positiveButtonClickListener.onClick(it) - dismiss() - } - } - if (negativeButtonClickListener != null) { - binding.bottomSheetCancelButton.isVisible = true - negativeButtonLabel?.let { buttonLbl -> - binding.bottomSheetCancelButton.text = buttonLbl - } - binding.bottomSheetCancelButton.setOnClickListener { - negativeButtonClickListener.onClick(it) - dismiss() - } - } - if (onDismissListener != null) { - dialog.setOnDismissListener(onDismissListener) - } - } - } - ) - } - - override fun dismiss() { - super.dismiss() - behavior?.removeBottomSheetCallback(bottomSheetCallback) - } - - class Builder(val context: Context) { - - private var title: String? = null - private var message: String? = null - private var positiveButtonLabel: String? = null - private var negativeButtonLabel: String? = null - private var positiveButtonClickListener: View.OnClickListener? = null - private var negativeButtonClickListener: View.OnClickListener? = null - private var onDismissListener: OnDismissListener? = null - - fun setTitleRes(@StringRes titleRes: Int): Builder { - this.title = context.resources.getString(titleRes) - return this - } - - fun setTitle(title: String): Builder { - this.title = title - return this - } - - fun setMessageRes(@StringRes messageRes: Int): Builder { - this.message = context.resources.getString(messageRes) - return this - } - - fun setMessage(message: String): Builder { - this.message = message - return this - } - - fun setPositiveButtonClickListener( - buttonLabel: String? = null, - listener: View.OnClickListener, - ): Builder { - this.positiveButtonClickListener = listener - this.positiveButtonLabel = buttonLabel - return this - } - - fun setNegativeButtonClickListener( - buttonLabel: String? = null, - listener: View.OnClickListener, - ): Builder { - this.negativeButtonClickListener = listener - this.negativeButtonLabel = buttonLabel - return this - } - - fun setOnDismissListener(onDismissListener: OnDismissListener): Builder { - this.onDismissListener = onDismissListener - return this - } - - fun build(): BasicBottomSheet { - require(message != null) { "Message needs to be set" } - return BasicBottomSheet( - title, - message!!, - positiveButtonLabel, - negativeButtonLabel, - positiveButtonClickListener, - negativeButtonClickListener, - onDismissListener, - ) - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt b/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt deleted file mode 100644 index 2a1d3ebd..00000000 --- a/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package app.passwordstore.ui.git.config - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.view.MenuItem -import android.view.View -import androidx.core.os.postDelayed -import androidx.core.view.isVisible -import androidx.core.widget.doOnTextChanged -import androidx.lifecycle.lifecycleScope -import app.passwordstore.R -import app.passwordstore.data.repo.PasswordRepository -import app.passwordstore.databinding.ActivityGitCloneBinding -import app.passwordstore.ui.dialogs.BasicBottomSheet -import app.passwordstore.ui.git.base.BaseGitActivity -import app.passwordstore.util.extensions.snackbar -import app.passwordstore.util.extensions.viewBinding -import app.passwordstore.util.settings.AuthMode -import app.passwordstore.util.settings.GitSettings -import app.passwordstore.util.settings.Protocol -import com.github.michaelbull.result.fold -import com.github.michaelbull.result.onFailure -import com.github.michaelbull.result.runCatching -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.snackbar.Snackbar -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import logcat.LogPriority.ERROR -import logcat.asLog -import logcat.logcat - -/** - * Activity that encompasses both the initial clone as well as editing the server config for future - * changes. - */ -class GitServerConfigActivity : BaseGitActivity() { - - private val binding by viewBinding(ActivityGitCloneBinding::inflate) - - private lateinit var newAuthMode: AuthMode - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val isClone = intent?.extras?.getBoolean("cloning") ?: false - if (isClone) { - binding.saveButton.text = getString(R.string.clone_button) - } - setContentView(binding.root) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - - newAuthMode = gitSettings.authMode - - binding.authModeGroup.apply { - when (newAuthMode) { - AuthMode.SshKey -> check(binding.authModeSshKey.id) - AuthMode.Password -> check(binding.authModePassword.id) - AuthMode.None -> clearChecked() - } - addOnButtonCheckedListener { _, _, _ -> - if (checkedButtonIds.isEmpty()) { - newAuthMode = AuthMode.None - } else { - when (checkedButtonId) { - binding.authModeSshKey.id -> newAuthMode = AuthMode.SshKey - binding.authModePassword.id -> newAuthMode = AuthMode.Password - View.NO_ID -> newAuthMode = AuthMode.None - } - } - } - } - - binding.serverUrl.setText( - gitSettings.url.also { - if (it.isNullOrEmpty()) return@also - setAuthModes(it.startsWith("http://") || it.startsWith("https://")) - } - ) - - binding.serverUrl.doOnTextChanged { text, _, _, _ -> - if (text.isNullOrEmpty()) return@doOnTextChanged - setAuthModes(text.startsWith("http://") || text.startsWith("https://")) - } - - binding.clearHostKeyButton.isVisible = gitSettings.hasSavedHostKey() - binding.clearHostKeyButton.setOnClickListener { - gitSettings.clearSavedHostKey() - Snackbar.make( - binding.root, - getString(R.string.clear_saved_host_key_success), - Snackbar.LENGTH_LONG, - ) - .show() - it.isVisible = false - } - binding.saveButton.setOnClickListener { - val newUrl = binding.serverUrl.text.toString().trim() - // If url is of type john_doe@example.org:12435/path/to/repo, then not adding `ssh://` - // in the beginning will cause the port to be seen as part of the path. Let users know - // about it and offer a quickfix. - if (newUrl.contains(PORT_REGEX)) { - if (newUrl.startsWith("https://")) { - BasicBottomSheet.Builder(this) - .setTitleRes(R.string.https_scheme_with_port_title) - .setMessageRes(R.string.https_scheme_with_port_message) - .setPositiveButtonClickListener { - binding.serverUrl.setText(newUrl.replace(PORT_REGEX, "/")) - } - .build() - .show(supportFragmentManager, "SSH_SCHEME_WARNING") - return@setOnClickListener - } else if (!newUrl.startsWith("ssh://")) { - BasicBottomSheet.Builder(this) - .setTitleRes(R.string.ssh_scheme_needed_title) - .setMessageRes(R.string.ssh_scheme_needed_message) - .setPositiveButtonClickListener { - @Suppress("SetTextI18n") binding.serverUrl.setText("ssh://$newUrl") - } - .build() - .show(supportFragmentManager, "SSH_SCHEME_WARNING") - return@setOnClickListener - } - } - if (newUrl.startsWith("git://")) { - BasicBottomSheet.Builder(this) - .setTitleRes(R.string.git_scheme_disallowed_title) - .setMessageRes(R.string.git_scheme_disallowed_message) - .setPositiveButtonClickListener {} - .build() - .show(supportFragmentManager, "SSH_SCHEME_WARNING") - return@setOnClickListener - } - when ( - val updateResult = - gitSettings.updateConnectionSettingsIfValid( - newAuthMode = newAuthMode, - newUrl = binding.serverUrl.text.toString().trim(), - ) - ) { - GitSettings.UpdateConnectionSettingsResult.FailedToParseUrl -> { - Snackbar.make( - binding.root, - getString(R.string.git_server_config_save_error), - Snackbar.LENGTH_LONG, - ) - .show() - } - is GitSettings.UpdateConnectionSettingsResult.MissingUsername -> { - when (updateResult.newProtocol) { - Protocol.Https -> - BasicBottomSheet.Builder(this) - .setTitleRes(R.string.ssh_scheme_needed_title) - .setMessageRes(R.string.git_server_config_save_missing_username_https) - .setPositiveButtonClickListener {} - .build() - .show(supportFragmentManager, "HTTPS_MISSING_USERNAME") - Protocol.Ssh -> - BasicBottomSheet.Builder(this) - .setTitleRes(R.string.ssh_scheme_needed_title) - .setMessageRes(R.string.git_server_config_save_missing_username_ssh) - .setPositiveButtonClickListener {} - .build() - .show(supportFragmentManager, "SSH_MISSING_USERNAME") - } - } - GitSettings.UpdateConnectionSettingsResult.Valid -> { - if (isClone && PasswordRepository.repository == null) PasswordRepository.initialize() - if (!isClone) { - Snackbar.make( - binding.root, - getString(R.string.git_server_config_save_success), - Snackbar.LENGTH_SHORT, - ) - .show() - Handler(Looper.getMainLooper()).postDelayed(500) { finish() } - } else { - cloneRepository() - } - } - is GitSettings.UpdateConnectionSettingsResult.AuthModeMismatch -> { - val message = - getString( - R.string.git_server_config_save_auth_mode_mismatch, - updateResult.newProtocol, - updateResult.validModes.joinToString(", "), - ) - Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show() - } - } - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - onBackPressedDispatcher.onBackPressed() - true - } - else -> super.onOptionsItemSelected(item) - } - } - - private fun setAuthModes(isHttps: Boolean) = - with(binding) { - if (isHttps) { - authModeSshKey.isVisible = false - authModePassword.isVisible = true - if (authModeGroup.checkedButtonId != authModePassword.id) authModeGroup.clearChecked() - } else { - authModeSshKey.isVisible = true - authModePassword.isVisible = true - if (authModeGroup.checkedButtonId == View.NO_ID) authModeGroup.check(authModeSshKey.id) - } - } - - /** Clones the repository, the directory exists, deletes it */ - private fun cloneRepository() { - val localDir = - requireNotNull(PasswordRepository.getRepositoryDirectory()) { - "Repository directory must be set before cloning" - } - val localDirFiles = localDir.listFiles() ?: emptyArray() - // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder - if ( - localDir.exists() && - localDirFiles.isNotEmpty() && - !(localDirFiles.size == 1 && localDirFiles[0].name == ".git") - ) { - MaterialAlertDialogBuilder(this) - .setTitle(R.string.dialog_delete_title) - .setMessage(resources.getString(R.string.dialog_delete_msg, localDir.toString())) - .setCancelable(false) - .setPositiveButton(R.string.dialog_delete) { dialog, _ -> - runCatching { - lifecycleScope.launch { - val snackbar = - snackbar( - message = getString(R.string.delete_directory_progress_text), - length = Snackbar.LENGTH_INDEFINITE, - ) - withContext(dispatcherProvider.io()) { - localDir.deleteRecursively() - localDir.mkdirs() - } - snackbar.dismiss() - launchGitOperation(GitOp.CLONE) - .fold( - success = { - setResult(RESULT_OK) - finish() - }, - failure = { err -> promptOnErrorHandler(err) { finish() } }, - ) - } - } - .onFailure { e -> - e.printStackTrace() - MaterialAlertDialogBuilder(this).setMessage(e.message).show() - } - dialog.cancel() - } - .setNegativeButton(R.string.dialog_do_not_delete) { dialog, _ -> dialog.cancel() } - .show() - } else { - runCatching { - // Silently delete & replace the lone .git folder if it exists - if (localDir.exists() && localDirFiles.size == 1 && localDirFiles[0].name == ".git") { - localDir.deleteRecursively() - } - } - .onFailure { e -> - logcat(ERROR) { e.asLog() } - MaterialAlertDialogBuilder(this).setMessage(e.message).show() - } - lifecycleScope.launch { - launchGitOperation(GitOp.CLONE) - .fold( - success = { - setResult(RESULT_OK) - finish() - }, - failure = { promptOnErrorHandler(it) }, - ) - } - } - } - - companion object { - - private val PORT_REGEX = ":[0-9]{1,5}/".toRegex() - - fun createCloneIntent(context: Context): Intent { - return Intent(context, GitServerConfigActivity::class.java).apply { - putExtra("cloning", true) - } - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt b/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt deleted file mode 100644 index 709c2302..00000000 --- a/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.onboarding.activity - -import android.os.Bundle -import androidx.activity.addCallback -import androidx.appcompat.app.AppCompatActivity -import app.passwordstore.R -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - supportActionBar?.hide() - val callback = onBackPressedDispatcher.addCallback(enabled = false) { finishAffinity() } - supportFragmentManager.addOnBackStackChangedListener { - if (supportFragmentManager.backStackEntryCount == 0) { - callback.isEnabled = true - } - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/CloneFragment.kt deleted file mode 100644 index a9d933b6..00000000 --- a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/CloneFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.onboarding.fragments - -import android.os.Bundle -import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.edit -import androidx.fragment.app.Fragment -import app.passwordstore.R -import app.passwordstore.data.repo.PasswordRepository -import app.passwordstore.databinding.FragmentCloneBinding -import app.passwordstore.ui.git.config.GitServerConfigActivity -import app.passwordstore.util.extensions.finish -import app.passwordstore.util.extensions.performTransactionWithBackStack -import app.passwordstore.util.extensions.sharedPrefs -import app.passwordstore.util.extensions.unsafeLazy -import app.passwordstore.util.extensions.viewBinding -import app.passwordstore.util.settings.PreferenceKeys -import com.github.michaelbull.result.onFailure -import com.github.michaelbull.result.runCatching -import logcat.LogPriority.ERROR -import logcat.asLog -import logcat.logcat - -class CloneFragment : Fragment(R.layout.fragment_clone) { - - private val binding by viewBinding(FragmentCloneBinding::bind) - - private val settings by unsafeLazy { requireActivity().applicationContext.sharedPrefs } - - private val cloneAction = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == AppCompatActivity.RESULT_OK) { - settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } - finish() - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.cloneRemote.setOnClickListener { cloneToHiddenDir() } - binding.createLocal.setOnClickListener { createRepository() } - } - - /** Clones a remote Git repository to the app's private directory */ - private fun cloneToHiddenDir() { - cloneAction.launch(GitServerConfigActivity.createCloneIntent(requireContext())) - } - - private fun createRepository() { - val localDir = PasswordRepository.getRepositoryDirectory() - runCatching { - check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" } - PasswordRepository.createRepository(localDir) - if (!PasswordRepository.isInitialized) { - PasswordRepository.initialize() - } - parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance()) - } - .onFailure { e -> - logcat(ERROR) { e.asLog() } - if (!localDir.delete()) { - logcat { "Failed to delete local repository: $localDir" } - } - finish() - } - } - - companion object { - - fun newInstance(): CloneFragment = CloneFragment() - } -} diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt deleted file mode 100644 index 64c2e124..00000000 --- a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.onboarding.fragments - -import android.content.SharedPreferences -import android.os.Bundle -import android.view.View -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.edit -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import app.passwordstore.R -import app.passwordstore.data.repo.PasswordRepository -import app.passwordstore.databinding.FragmentKeySelectionBinding -import app.passwordstore.injection.prefs.SettingsPreferences -import app.passwordstore.ui.pgp.PGPKeyListActivity -import app.passwordstore.util.coroutines.DispatcherProvider -import app.passwordstore.util.extensions.commitChange -import app.passwordstore.util.extensions.finish -import app.passwordstore.util.extensions.snackbar -import app.passwordstore.util.extensions.viewBinding -import app.passwordstore.util.settings.PreferenceKeys -import com.google.android.material.snackbar.Snackbar -import dagger.hilt.android.AndroidEntryPoint -import java.io.File -import javax.inject.Inject -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -@AndroidEntryPoint -class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) { - - @Inject @SettingsPreferences lateinit var settings: SharedPreferences - @Inject lateinit var dispatcherProvider: DispatcherProvider - private val binding by viewBinding(FragmentKeySelectionBinding::bind) - private val gpgKeySelectAction = - registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == AppCompatActivity.RESULT_OK) { - val data = result.data ?: return@registerForActivityResult - val selectedKey = - data.getStringExtra(PGPKeyListActivity.EXTRA_SELECTED_KEY) - ?: return@registerForActivityResult - lifecycleScope.launch { - withContext(dispatcherProvider.io()) { - val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id") - gpgIdentifierFile.writeText(selectedKey) - } - settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } - requireActivity() - .commitChange(getString(R.string.git_commit_gpg_id, getString(R.string.app_name))) - finish() - } - } else { - requireActivity() - .snackbar( - message = getString(R.string.gpg_key_select_mandatory), - length = Snackbar.LENGTH_LONG, - ) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.selectKey.setOnClickListener { - gpgKeySelectAction.launch(PGPKeyListActivity.newSelectionActivity(requireContext())) - } - } - - companion object { - - fun newInstance() = KeySelectionFragment() - } -} diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/WelcomeFragment.kt b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/WelcomeFragment.kt deleted file mode 100644 index 44881c64..00000000 --- a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/WelcomeFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.onboarding.fragments - -import android.os.Bundle -import android.view.View -import androidx.annotation.Keep -import androidx.fragment.app.Fragment -import app.passwordstore.R -import app.passwordstore.databinding.FragmentWelcomeBinding -import app.passwordstore.ui.settings.SettingsActivity -import app.passwordstore.util.extensions.launchActivity -import app.passwordstore.util.extensions.performTransactionWithBackStack -import app.passwordstore.util.extensions.viewBinding - -@Keep -class WelcomeFragment : Fragment(R.layout.fragment_welcome) { - - private val binding by viewBinding(FragmentWelcomeBinding::bind) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.letsGo.setOnClickListener { - parentFragmentManager.performTransactionWithBackStack(CloneFragment.newInstance()) - } - binding.settingsButton.setOnClickListener { - requireActivity().launchActivity(SettingsActivity::class.java) - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyImportActivity.kt b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyImportActivity.kt deleted file mode 100644 index d276c552..00000000 --- a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyImportActivity.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("BlockingMethodInNonBlockingContext") - -package app.passwordstore.ui.pgp - -import android.os.Bundle -import androidx.activity.result.contract.ActivityResultContracts.OpenDocument -import androidx.appcompat.app.AppCompatActivity -import app.passwordstore.R -import app.passwordstore.crypto.KeyUtils.tryGetId -import app.passwordstore.crypto.PGPKey -import app.passwordstore.crypto.PGPKeyManager -import app.passwordstore.crypto.errors.KeyAlreadyExistsException -import com.github.michaelbull.result.Result -import com.github.michaelbull.result.runCatching -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject -import kotlinx.coroutines.runBlocking - -@AndroidEntryPoint -class PGPKeyImportActivity : AppCompatActivity() { - - /** - * A [ByteArray] containing the contents of the previously selected file. This is necessary for - * the replacement case where we do not want users to have to pick the file again. - */ - private var lastBytes: ByteArray? = null - @Inject lateinit var keyManager: PGPKeyManager - - private val pgpKeyImportAction = - registerForActivityResult(OpenDocument()) { uri -> - runCatching { - if (uri == null) { - return@runCatching null - } - val keyInputStream = - contentResolver.openInputStream(uri) - ?: throw IllegalStateException("Failed to open selected file") - val bytes = keyInputStream.use { `is` -> `is`.readBytes() } - importKey(bytes, false) - } - .run(::handleImportResult) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - pgpKeyImportAction.launch(arrayOf("*/*")) - } - - override fun onDestroy() { - lastBytes = null - super.onDestroy() - } - - private fun importKey(bytes: ByteArray, replace: Boolean): PGPKey? { - lastBytes = bytes - val (key, error) = runBlocking { keyManager.addKey(PGPKey(bytes), replace = replace) } - if (replace) { - lastBytes = null - } - if (error != null) throw error - return key - } - - private fun handleImportResult(result: Result<PGPKey?, Throwable>) { - if (result.isOk) { - val key = result.value - if (key == null) { - setResult(RESULT_CANCELED) - finish() - // This return convinces Kotlin that the control flow for `key == null` definitely - // terminates here and allows for a smart cast below. - return - } - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.pgp_key_import_succeeded)) - .setMessage(getString(R.string.pgp_key_import_succeeded_message, tryGetId(key))) - .setPositiveButton(android.R.string.ok) { _, _ -> - setResult(RESULT_OK) - finish() - } - .setCancelable(false) - .show() - } else { - if (result.error is KeyAlreadyExistsException && lastBytes != null) { - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.pgp_key_import_failed)) - .setMessage(getString(R.string.pgp_key_import_failed_replace_message)) - .setPositiveButton(R.string.dialog_yes) { _, _ -> - handleImportResult(runCatching { importKey(lastBytes!!, replace = true) }) - } - .setNegativeButton(R.string.dialog_no) { _, _ -> finish() } - .setCancelable(false) - .show() - } else { - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.pgp_key_import_failed)) - .setMessage(result.error.message) - .setPositiveButton(android.R.string.ok) { _, _ -> finish() } - .setCancelable(false) - .show() - } - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt deleted file mode 100644 index 312f5fb0..00000000 --- a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.pgp - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import app.passwordstore.R -import app.passwordstore.crypto.PGPIdentifier -import app.passwordstore.ui.compose.theme.APSTheme -import app.passwordstore.ui.compose.theme.SpacingLarge -import app.passwordstore.util.extensions.conditional -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList - -@Composable -fun KeyList( - identifiers: ImmutableList<PGPIdentifier>, - onItemClick: (identifier: PGPIdentifier) -> Unit, - modifier: Modifier = Modifier, - onKeySelected: ((identifier: PGPIdentifier) -> Unit)? = null, -) { - if (identifiers.isEmpty()) { - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image( - painter = painterResource(id = R.drawable.ic_launcher_foreground), - contentDescription = "Password Store logo", - ) - Text(stringResource(R.string.pgp_key_manager_no_keys_guidance)) - } - } else { - LazyColumn(modifier = modifier) { - items(identifiers) { identifier -> - KeyItem(identifier = identifier, onItemClick = onItemClick, onKeySelected = onKeySelected) - } - } - } -} - -@Composable -private fun KeyItem( - identifier: PGPIdentifier, - onItemClick: (identifier: PGPIdentifier) -> Unit, - modifier: Modifier = Modifier, - onKeySelected: ((identifier: PGPIdentifier) -> Unit)? = null, -) { - var isDeleting by remember { mutableStateOf(false) } - DeleteConfirmationDialog( - isDeleting = isDeleting, - onDismiss = { isDeleting = false }, - onConfirm = { - onItemClick(identifier) - isDeleting = false - }, - ) - val label = - when (identifier) { - is PGPIdentifier.KeyId -> identifier.id.toString() - is PGPIdentifier.UserId -> identifier.email - } - Row( - modifier = - modifier.padding(SpacingLarge).fillMaxWidth().conditional(onKeySelected != null) { - clickable { onKeySelected?.invoke(identifier) } - }, - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = label, - modifier = Modifier.weight(1f), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - if (onKeySelected == null) { - IconButton(onClick = { isDeleting = true }) { - Icon( - painter = painterResource(R.drawable.ic_delete_24dp), - stringResource(id = R.string.delete), - ) - } - } - } -} - -@Suppress("NOTHING_TO_INLINE") -@Composable -private inline fun DeleteConfirmationDialog( - isDeleting: Boolean, - noinline onDismiss: () -> Unit, - noinline onConfirm: () -> Unit, -) { - if (isDeleting) { - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text(text = stringResource(R.string.pgp_key_manager_delete_confirmation_dialog_title)) - }, - confirmButton = { - TextButton(onClick = onConfirm) { Text(text = stringResource(R.string.dialog_yes)) } - }, - dismissButton = { - TextButton(onClick = onDismiss) { Text(text = stringResource(R.string.dialog_no)) } - }, - ) - } -} - -@Preview -@Composable -private fun KeyListPreview() { - APSTheme { - Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { - KeyList( - identifiers = - listOfNotNull( - PGPIdentifier.fromString("ultramicroscopicsilicovolcanoconiosis@example.com"), - PGPIdentifier.fromString("0xB950AE2813841585"), - ) - .toPersistentList(), - onItemClick = {}, - ) - } - } -} - -@Preview -@Composable -private fun EmptyKeyListPreview() { - APSTheme { - Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { - KeyList(identifiers = persistentListOf(), onItemClick = {}) - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt deleted file mode 100644 index 77b9aaf1..00000000 --- a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.pgp - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import androidx.activity.viewModels -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import app.passwordstore.R -import app.passwordstore.ui.APSAppBar -import app.passwordstore.ui.compose.theme.APSTheme -import app.passwordstore.util.viewmodel.PGPKeyListViewModel -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class PGPKeyListActivity : ComponentActivity() { - - private val viewModel: PGPKeyListViewModel by viewModels() - private val keyImportAction = - registerForActivityResult(StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - viewModel.updateKeySet() - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val isSelecting = intent.extras?.getBoolean(EXTRA_KEY_SELECTION) ?: false - setContent { - APSTheme { - Scaffold( - topBar = { - APSAppBar( - title = stringResource(R.string.activity_label_pgp_key_manager), - navigationIcon = painterResource(R.drawable.ic_arrow_back_black_24dp), - onNavigationIconClick = { finish() }, - backgroundColor = MaterialTheme.colorScheme.surface, - ) - }, - floatingActionButton = { - FloatingActionButton( - onClick = { keyImportAction.launch(Intent(this, PGPKeyImportActivity::class.java)) } - ) { - Icon( - painter = painterResource(R.drawable.ic_add_48dp), - stringResource(R.string.pref_import_pgp_key_title), - ) - } - }, - ) { paddingValues -> - KeyList( - identifiers = viewModel.keys, - onItemClick = viewModel::deleteKey, - modifier = Modifier.padding(paddingValues), - onKeySelected = - if (isSelecting) { - { identifier -> - val result = Intent() - result.putExtra(EXTRA_SELECTED_KEY, identifier.toString()) - setResult(RESULT_OK, result) - finish() - } - } else null, - ) - } - } - } - } - - companion object { - const val EXTRA_SELECTED_KEY = "SELECTED_KEY" - private const val EXTRA_KEY_SELECTION = "KEY_SELECTION_MODE" - - fun newSelectionActivity(context: Context): Intent { - val intent = Intent(context, PGPKeyListActivity::class.java) - intent.putExtra(EXTRA_KEY_SELECTION, true) - return intent - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt deleted file mode 100644 index 3f04e35e..00000000 --- a/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package app.passwordstore.ui.sshkeygen - -import android.app.Dialog -import android.content.Intent -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import app.passwordstore.R -import app.passwordstore.util.git.sshj.SshKey -import com.google.android.material.dialog.MaterialAlertDialogBuilder - -class ShowSshKeyFragment : DialogFragment() { - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val activity = requireActivity() - val publicKey = SshKey.sshPublicKey - return MaterialAlertDialogBuilder(requireActivity()).run { - setMessage(getString(R.string.ssh_keygen_message, publicKey)) - setTitle(R.string.your_public_key) - setNegativeButton(R.string.ssh_keygen_later) { _, _ -> - (activity as? SshKeyGenActivity)?.finish() - } - setPositiveButton(R.string.ssh_keygen_share) { _, _ -> - val sendIntent = - Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, publicKey) - } - startActivity(Intent.createChooser(sendIntent, null)) - (activity as? SshKeyGenActivity)?.finish() - } - create() - } - } -} diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt deleted file mode 100644 index 618a4be0..00000000 --- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package app.passwordstore.ui.sshkeygen - -import android.content.SharedPreferences -import android.os.Bundle -import android.security.keystore.UserNotAuthenticatedException -import android.view.MenuItem -import android.view.View -import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.edit -import androidx.core.content.getSystemService -import androidx.lifecycle.lifecycleScope -import app.passwordstore.R -import app.passwordstore.databinding.ActivitySshKeygenBinding -import app.passwordstore.injection.prefs.GitPreferences -import app.passwordstore.util.auth.BiometricAuthenticator -import app.passwordstore.util.auth.BiometricAuthenticator.Result -import app.passwordstore.util.coroutines.DispatcherProvider -import app.passwordstore.util.extensions.keyguardManager -import app.passwordstore.util.extensions.viewBinding -import app.passwordstore.util.git.sshj.SshKey -import com.github.michaelbull.result.fold -import com.github.michaelbull.result.runCatching -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -private enum class KeyGenType(val generateKey: suspend (requireAuthentication: Boolean) -> Unit) { - Rsa({ requireAuthentication -> - SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Rsa, requireAuthentication) - }), - Ecdsa({ requireAuthentication -> - SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Ecdsa, requireAuthentication) - }), - Ed25519({ requireAuthentication -> - SshKey.generateKeystoreWrappedEd25519Key(requireAuthentication) - }), -} - -@AndroidEntryPoint -class SshKeyGenActivity : AppCompatActivity() { - - private var keyGenType = KeyGenType.Ecdsa - private val binding by viewBinding(ActivitySshKeygenBinding::inflate) - @GitPreferences @Inject lateinit var gitPrefs: SharedPreferences - @Inject lateinit var dispatcherProvider: DispatcherProvider - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(binding.root) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - with(binding) { - generate.setOnClickListener { - if (SshKey.exists) { - MaterialAlertDialogBuilder(this@SshKeyGenActivity).run { - setTitle(R.string.ssh_keygen_existing_title) - setMessage(R.string.ssh_keygen_existing_message) - setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ -> - lifecycleScope.launch { generate() } - } - setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> - setResult(RESULT_CANCELED) - finish() - } - show() - } - } else { - lifecycleScope.launch { generate() } - } - } - keyTypeGroup.check(R.id.key_type_ecdsa) - keyTypeExplanation.setText(R.string.ssh_keygen_explanation_ecdsa) - keyTypeGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> - if (isChecked) { - keyGenType = - when (checkedId) { - R.id.key_type_ed25519 -> KeyGenType.Ed25519 - R.id.key_type_ecdsa -> KeyGenType.Ecdsa - R.id.key_type_rsa -> KeyGenType.Rsa - else -> throw IllegalStateException("Impossible key type selection") - } - keyTypeExplanation.setText( - when (keyGenType) { - KeyGenType.Ed25519 -> R.string.ssh_keygen_explanation_ed25519 - KeyGenType.Ecdsa -> R.string.ssh_keygen_explanation_ecdsa - KeyGenType.Rsa -> R.string.ssh_keygen_explanation_rsa - } - ) - } - } - keyRequireAuthentication.isEnabled = keyguardManager.isDeviceSecure - keyRequireAuthentication.isChecked = keyRequireAuthentication.isEnabled - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - onBackPressedDispatcher.onBackPressed() - true - } - else -> super.onOptionsItemSelected(item) - } - } - - private suspend fun generate() { - binding.generate.apply { - text = getString(R.string.ssh_key_gen_generating_progress) - isEnabled = false - } - binding.generate.text = getString(R.string.ssh_key_gen_generating_progress) - val result = runCatching { - withContext(dispatcherProvider.io()) { - val requireAuthentication = binding.keyRequireAuthentication.isChecked - if (requireAuthentication) { - val result = - withContext(dispatcherProvider.main()) { - suspendCoroutine { cont -> - BiometricAuthenticator.authenticate( - this@SshKeyGenActivity, - R.string.biometric_prompt_title_ssh_keygen, - ) { result -> - // Do not cancel on failed attempts as these are handled by the - // authenticator UI. - if (result !is Result.Retry) cont.resume(result) - } - } - } - if (result !is Result.Success) - throw UserNotAuthenticatedException(getString(R.string.biometric_auth_generic_failure)) - } - keyGenType.generateKey(requireAuthentication) - } - } - gitPrefs.edit { remove("ssh_key_local_passphrase") } - binding.generate.apply { - text = getString(R.string.ssh_keygen_generate) - isEnabled = true - } - result.fold( - success = { ShowSshKeyFragment().show(supportFragmentManager, "public_key") }, - failure = { e -> - e.printStackTrace() - MaterialAlertDialogBuilder(this) - .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() - }, - ) - hideKeyboard() - } - - private fun hideKeyboard() { - val imm = getSystemService<InputMethodManager>() ?: return - var view = currentFocus - if (view == null) { - view = View(this) - } - imm.hideSoftInputFromWindow(view.windowToken, 0) - } -} diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt deleted file mode 100644 index e4740741..00000000 --- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.ui.sshkeygen - -import android.net.Uri -import android.os.Bundle -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import app.passwordstore.R -import app.passwordstore.util.git.sshj.SshKey -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/app/passwordstore/util/viewmodel/PGPKeyListViewModel.kt b/app/src/main/java/app/passwordstore/util/viewmodel/PGPKeyListViewModel.kt deleted file mode 100644 index 5c7429cd..00000000 --- a/app/src/main/java/app/passwordstore/util/viewmodel/PGPKeyListViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.util.viewmodel - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import app.passwordstore.crypto.KeyUtils -import app.passwordstore.crypto.PGPIdentifier -import app.passwordstore.crypto.PGPKeyManager -import com.github.michaelbull.result.map -import com.github.michaelbull.result.onSuccess -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.launch - -@HiltViewModel -class PGPKeyListViewModel @Inject constructor(private val keyManager: PGPKeyManager) : ViewModel() { - var keys: ImmutableList<PGPIdentifier> by mutableStateOf(persistentListOf()) - - init { - updateKeySet() - } - - fun updateKeySet() { - viewModelScope.launch { - keyManager - .getAllKeys() - .map { keys -> keys.mapNotNull { key -> KeyUtils.tryGetEmail(key) } } - .onSuccess { keys = it.toPersistentList() } - } - } - - fun deleteKey(identifier: PGPIdentifier) { - viewModelScope.launch { - keyManager.removeKey(identifier) - updateKeySet() - } - } -} diff --git a/app/src/main/res/drawable/ic_add_48dp.xml b/app/src/main/res/drawable/ic_add_48dp.xml deleted file mode 100644 index 7192e79d..00000000 --- a/app/src/main/res/drawable/ic_add_48dp.xml +++ /dev/null @@ -1,15 +0,0 @@ -<!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:tint="?attr/colorControlNormal" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="#FFFFFF" - android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> -</vector> diff --git a/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml deleted file mode 100644 index 234364a9..00000000 --- a/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml +++ /dev/null @@ -1,15 +0,0 @@ -<!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:tint="?attr/colorControlNormal" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" /> -</vector> diff --git a/app/src/main/res/layout/activity_git_clone.xml b/app/src/main/res/layout/activity_git_clone.xml deleted file mode 100644 index b2f699b1..00000000 --- a/app/src/main/res/layout/activity_git_clone.xml +++ /dev/null @@ -1,94 +0,0 @@ -<!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/windowBackground" - android:padding="@dimen/activity_horizontal_margin" - tools:context="app.passwordstore.ui.git.config.GitServerConfigActivity"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/label_server_url" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:hint="@string/server_url" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/server_url" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:imeOptions="actionNext" - android:inputType="textWebEmailAddress" - android:nextFocusForward="@id/auth_mode_group" /> - - </com.google.android.material.textfield.TextInputLayout> - - <androidx.appcompat.widget.AppCompatTextView - android:id="@+id/label_auth_mode" - style="@style/TextAppearance.MaterialComponents.Headline6" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:layout_marginBottom="16dp" - android:text="@string/connection_mode" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/label_server_url" /> - - <com.google.android.material.button.MaterialButtonToggleGroup - android:id="@+id/auth_mode_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/label_auth_mode" - app:singleSelection="true"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/auth_mode_ssh_key" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/connection_mode_ssh_key" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/auth_mode_password" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/connection_mode_basic_authentication" /> - - </com.google.android.material.button.MaterialButtonToggleGroup> - - <com.google.android.material.button.MaterialButton - android:id="@+id/save_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/crypto_save" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/auth_mode_group" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/clear_host_key_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/clear_saved_host_key" - android:visibility="gone" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/auth_mode_group" - tools:visibility="visible" /> - </androidx.constraintlayout.widget.ConstraintLayout> -</ScrollView> diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml deleted file mode 100644 index d1153f5d..00000000 --- a/app/src/main/res/layout/activity_onboarding.xml +++ /dev/null @@ -1,11 +0,0 @@ -<!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/fragment_first_run" - android:name="app.passwordstore.ui.onboarding.fragments.WelcomeFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tag="welcome_fragment" /> diff --git a/app/src/main/res/layout/activity_ssh_keygen.xml b/app/src/main/res/layout/activity_ssh_keygen.xml deleted file mode 100644 index 49548b36..00000000 --- a/app/src/main/res/layout/activity_ssh_keygen.xml +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingLeft="@dimen/activity_horizontal_margin" - android:paddingTop="@dimen/activity_vertical_margin" - android:paddingRight="@dimen/activity_horizontal_margin"> - - <com.google.android.material.button.MaterialButtonToggleGroup - android:id="@+id/key_type_group" - style="@style/TextAppearance.MaterialComponents.Headline1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:selectionRequired="true" - app:singleSelection="true"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/key_type_rsa" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/ssh_keygen_label_rsa" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/key_type_ecdsa" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/ssh_keygen_label_ecdsa" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/key_type_ed25519" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/ssh_keygen_label_ed25519" /> - </com.google.android.material.button.MaterialButtonToggleGroup> - - <androidx.appcompat.widget.AppCompatTextView - android:id="@+id/key_type_explanation" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" /> - - <com.google.android.material.checkbox.MaterialCheckBox - android:id="@+id/key_require_authentication" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/ssh_keygen_require_authentication" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/generate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="8dp" - android:text="@string/ssh_keygen_generate" /> - </LinearLayout> -</ScrollView> diff --git a/app/src/main/res/layout/basic_bottom_sheet.xml b/app/src/main/res/layout/basic_bottom_sheet.xml deleted file mode 100644 index b45fc9c9..00000000 --- a/app/src/main/res/layout/basic_bottom_sheet.xml +++ /dev/null @@ -1,62 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingBottom="24dp"> - - <com.google.android.material.textview.MaterialTextView - android:id="@+id/bottom_sheet_title" - style="?attr/textAppearanceTitleLarge" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginTop="8dp" - android:visibility="gone" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:text="Bottom sheet title" /> - - <com.google.android.material.textview.MaterialTextView - android:id="@+id/bottom_sheet_message" - style="?attr/textAppearanceBodyLarge" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginTop="16dp" - android:layout_marginEnd="8dp" - app:layout_constraintTop_toBottomOf="@id/bottom_sheet_title" - tools:text="A long body of text that serves as the bottom sheet message" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/bottom_sheet_cancel_button" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="24dp" - android:layout_marginEnd="8dp" - android:text="@string/dialog_cancel" - android:visibility="gone" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/bottom_sheet_message" - tools:visibility="visible" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/bottom_sheet_ok_button" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="24dp" - android:layout_marginEnd="8dp" - android:text="@string/dialog_ok" - android:visibility="gone" - app:layout_constraintEnd_toStartOf="@id/bottom_sheet_cancel_button" - app:layout_constraintTop_toBottomOf="@id/bottom_sheet_message" - tools:visibility="visible" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_clone.xml b/app/src/main/res/layout/fragment_clone.xml deleted file mode 100644 index 9cb34761..00000000 --- a/app/src/main/res/layout/fragment_clone.xml +++ /dev/null @@ -1,96 +0,0 @@ -<!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/colorBackground"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/app_icon" - android:layout_width="64dp" - android:layout_height="64dp" - android:layout_marginStart="32dp" - android:layout_marginTop="@dimen/onboarding_icon_margin_top" - android:contentDescription="@string/app_icon_hint" - android:src="@mipmap/ic_launcher" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <androidx.appcompat.widget.AppCompatTextView - android:id="@+id/app_name" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_marginStart="@dimen/fab_compat_margin" - android:layout_marginEnd="@dimen/fab_compat_margin" - android:text="@string/app_name" - android:textAppearance="?attr/textAppearanceTitleLarge" - android:textStyle="bold" - app:layout_constraintBottom_toBottomOf="@id/app_icon" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/app_icon" - app:layout_constraintTop_toTopOf="@+id/app_icon" /> - - <TextView - android:id="@+id/repo_type" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="48dp" - android:layout_marginEnd="@dimen/fab_compat_margin" - android:text="@string/select_n_repository_type" - android:textAppearance="?attr/textAppearanceHeadlineSmall" - android:textStyle="bold" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintStart_toStartOf="@id/app_icon" - app:layout_constraintTop_toBottomOf="@id/app_icon" /> - - <TextView - android:id="@+id/tv_repo_type_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/onboarding_desc_margin_top" - android:layout_marginEnd="@dimen/fab_compat_margin" - android:text="@string/select_repo_type_text" - android:textAppearance="?attr/textAppearanceTitleSmall" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/repo_type" - app:layout_constraintTop_toBottomOf="@id/repo_type" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/clone_remote" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="@dimen/onboarding_button_margin_top" - android:layout_marginEnd="16dp" - android:maxWidth="300dp" - android:minWidth="100dp" - android:text="@string/clone_remote_repo" - app:layout_constraintBottom_toTopOf="@id/create_local" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tv_repo_type_text" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/create_local" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginBottom="128dp" - android:maxWidth="300dp" - android:minWidth="100dp" - android:text="@string/create_local_repo" - app:layout_constraintEnd_toEndOf="@id/clone_remote" - app:layout_constraintStart_toStartOf="@id/clone_remote" - app:layout_constraintTop_toBottomOf="@id/clone_remote" /> - - </androidx.constraintlayout.widget.ConstraintLayout> -</ScrollView> diff --git a/app/src/main/res/layout/fragment_key_selection.xml b/app/src/main/res/layout/fragment_key_selection.xml deleted file mode 100644 index b9141960..00000000 --- a/app/src/main/res/layout/fragment_key_selection.xml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/colorBackground" - android:orientation="vertical"> - - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/app_icon" - android:layout_width="64dp" - android:layout_height="64dp" - android:layout_marginStart="32dp" - android:layout_marginTop="@dimen/onboarding_icon_margin_top" - android:contentDescription="@string/app_icon_hint" - android:src="@mipmap/ic_launcher" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <androidx.appcompat.widget.AppCompatTextView - android:id="@+id/app_name" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_marginStart="@dimen/fab_compat_margin" - android:layout_marginEnd="@dimen/fab_compat_margin" - android:text="@string/app_name" - android:textAppearance="?attr/textAppearanceTitleLarge" - android:textStyle="bold" - app:layout_constraintBottom_toBottomOf="@id/app_icon" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/app_icon" - app:layout_constraintTop_toTopOf="@+id/app_icon" /> - - <TextView - android:id="@+id/gpg_key" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="48dp" - android:layout_marginEnd="@dimen/fab_compat_margin" - android:text="@string/select_gpg_key_title" - android:textAppearance="?attr/textAppearanceHeadlineSmall" - android:textStyle="bold" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintStart_toStartOf="@id/app_icon" - app:layout_constraintTop_toBottomOf="@id/app_icon" /> - - <TextView - android:id="@+id/gpg_key_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/onboarding_desc_margin_top" - android:layout_marginEnd="16dp" - android:text="@string/select_gpg_key_message" - android:textAppearance="?attr/textAppearanceTitleSmall" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/gpg_key" - app:layout_constraintTop_toBottomOf="@id/gpg_key" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/select_key" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="48dp" - android:layout_marginEnd="16dp" - android:maxWidth="300dp" - android:minWidth="100dp" - android:text="@string/gpg_key_select" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/gpg_key_text" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_welcome.xml b/app/src/main/res/layout/fragment_welcome.xml deleted file mode 100644 index c68cd56a..00000000 --- a/app/src/main/res/layout/fragment_welcome.xml +++ /dev/null @@ -1,67 +0,0 @@ -<!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/colorBackground" - android:orientation="vertical"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/settings_button" - style="?attr/borderlessButtonStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_alignParentEnd="true" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:text="@string/action_settings" - android:textAllCaps="true" - android:textColor="?attr/colorOnBackground" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/app_icon" - android:layout_width="100dp" - android:layout_height="100dp" - android:contentDescription="@string/app_icon_hint" - android:src="@mipmap/ic_launcher" - android:transitionName="transition_first_app_icon" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <androidx.appcompat.widget.AppCompatTextView - android:id="@+id/app_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_marginTop="16dp" - android:text="@string/app_name" - android:textAppearance="?attr/textAppearanceTitleLarge" - android:textStyle="bold" - android:transitionName="transition_first_run_app_name" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/app_icon" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/lets_go" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="48dp" - android:layout_marginEnd="16dp" - android:maxWidth="300dp" - android:minWidth="100dp" - android:text="@string/let_s_go" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/app_name" /> -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a9fa4b00..493391a8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -171,7 +171,6 @@ <string name="show_password_pref_title">Zeige das Passwort.</string> <string name="show_password_pref_summary">Soll das entschlüsselte Passwort sichtbar sein? Dies deaktiviert nicht das Kopieren.</string> <string name="send_plaintext_password_to">Passwort unverschlüsselt senden an…</string> - <string name="app_icon_hint">App-Icon</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Eintrag zum Ausfüllen auswählen</string> <string name="oreo_autofill_strict_domain_search">Phishing-resistente Suche</string> @@ -283,16 +282,12 @@ <string name="git_break_out_of_detached_success">Es gab einen Konflikt während des Rebase-Prozesses. Ihr lokaler Branch %1$s wurde auf einen anderen Branch namens %2$s gepusht.\nNutzen Sie diesen Branch, um den Konflikt auf dem Server zu beheben</string> <string name="git_break_out_of_detached_unneeded">Das Repository befindet sich nicht im Rebase-Prozess; es ist nicht nötig, den Zustand auf einen weiteren Branch zu pushen.</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">Um fortzufahren, muss ein PGP-Schlüssel ausgewählt werden.</string> <string name="folder_creation_err_file_exists">Dateiname bereits vergeben</string> <string name="folder_creation_err_folder_exists">Ein Ordner mit diesem Namen existiert bereits.</string> <!-- Onboarding flow --> - <string name="let_s_go">Los geht\'s</string> - <string name="select_n_repository_type">Wählen Sie einen \nRepository-Typ.</string> <string name="select_repo_type_text">Wählen Sie aus, ob Sie ein lokales Repository anlegen, oder ein Remote-Repository klonen möchten.</string> <string name="clone_remote_repo">Remote-Repository klonen</string> <string name="create_local_repo">Lokales Repository anlegen</string> - <string name="select_gpg_key_title">Wählen Sie einen\nPGP-Schlüssel.</string> <string name="select_gpg_key_message">Wählen einen PGP-Schlüssel, mit dem der Store initialisiert werden soll.</string> <string name="gpg_key_select">Schlüssel auswählen</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d41048f8..d2a469e7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -159,7 +159,6 @@ <string name="show_password_pref_title">Montrer le mot de passe</string> <string name="show_password_pref_summary">Contrôle la visibilité des mots de passe une fois déchifrées, ceci n\'empêche pas de copier le mot de passe</string> <string name="send_plaintext_password_to">Envoyer le mot de passe en clair via…</string> - <string name="app_icon_hint">Icône de l\'application</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Sélectionnez l\'entrée à remplir</string> <string name="oreo_autofill_strict_domain_search">Recherche protégée contre le hameçonnage</string> @@ -271,12 +270,9 @@ <string name="folder_creation_err_file_exists">Un fichier portant ce nom existe déjà</string> <string name="folder_creation_err_folder_exists">Un dossier portant ce nom existe déjà</string> <!-- Onboarding flow --> - <string name="let_s_go">Allons-y</string> - <string name="select_n_repository_type">Sélectionnez le type de dépôt \n</string> <string name="select_repo_type_text">Sélectionnez si vous voulez créer un dépôt local ou cloner un dépôt distant.</string> <string name="clone_remote_repo">Cloner le dépôt distant</string> <string name="create_local_repo">Créer un dépôt local</string> - <string name="select_gpg_key_title">Sélectionnez\nla clé GPG</string> <string name="select_gpg_key_message">Sélectionnez une clé GPG pour initialiser votre magasin</string> <string name="gpg_key_select">Sélectionner la clé</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 3fb321e7..ad4c663d 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -172,7 +172,6 @@ <string name="show_password_pref_title">Mostrar contrasinal</string> <string name="show_password_pref_summary">Controlar a visibilidade do contrasinal unha vez descifrado, esto non desactiva o copiado do contrasinal</string> <string name="send_plaintext_password_to">Enviar contrasinal como texto plano usando…</string> - <string name="app_icon_hint">Icona da app</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Elixe o elemento a completar</string> <string name="oreo_autofill_strict_domain_search">Busca resistente ao phishing</string> @@ -285,16 +284,12 @@ a app desde unha fonte de confianza, como a Play Store, Amazon Appstore, F-Droid <string name="git_break_out_of_detached_success">Hai un conflito ó realizar rebase. A túa póla local %1$s fixo push a outra póla chamada %2$s\nUsa esta póla para resolver o conflito na túa computadora</string> <string name="git_break_out_of_detached_unneeded">O repositorio non cambiou de base, non é preciso cambiar a outra póla</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">Hai que elexir unha chave GPG para continuar</string> <string name="folder_creation_err_file_exists">Xa existe un ficheiro con ese nome</string> <string name="folder_creation_err_folder_exists">Xa existe un cartafol con ese nome</string> <!-- Onboarding flow --> - <string name="let_s_go">Imos alá</string> - <string name="select_n_repository_type">Escolle \nTipo de repositorio</string> <string name="select_repo_type_text">Escolle se queres crear un repositorio local ou clonar un remoto.</string> <string name="clone_remote_repo">Clonar repositorio remoto</string> <string name="create_local_repo">Crear repositorio local</string> - <string name="select_gpg_key_title">Elixe\nChave\nGPG</string> <string name="select_gpg_key_message">Elixe a chave GPG coa que queres iniciar a almacenaxe</string> <string name="gpg_key_select">Elixe chave</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-hdpi/dimens.xml b/app/src/main/res/values-hdpi/dimens.xml deleted file mode 100644 index f9a452aa..00000000 --- a/app/src/main/res/values-hdpi/dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<resources> - <dimen name="onboarding_icon_margin_top">50dp</dimen> - <dimen name="onboarding_button_margin_top">70dp</dimen> - <dimen name="onboarding_desc_margin_top">48dp</dimen> -</resources> diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 72bcb009..8edc9d3d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -154,7 +154,6 @@ <string name="show_password_pref_title">Mostra la password</string> <string name="show_password_pref_summary">Controlla la visibilità delle password una volta decrittografate. Questo non disabilita la copia negli appunti.</string> <string name="send_plaintext_password_to">Invia password come testo semplice usando…</string> - <string name="app_icon_hint">Icona app</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Seleziona la voce da compilare</string> <string name="oreo_autofill_strict_domain_search">Ricerca resistente al phishing</string> @@ -265,12 +264,9 @@ <string name="folder_creation_err_file_exists">Un file con quel nome esiste già</string> <string name="folder_creation_err_folder_exists">Una cartella con quel nome esiste già</string> <!-- Onboarding flow --> - <string name="let_s_go">Andiamo</string> - <string name="select_n_repository_type">Seleziona \nTipo di repository</string> <string name="select_repo_type_text">Seleziona se vuoi creare un repo locale o clonarne uno remoto.</string> <string name="clone_remote_repo">Clona repo remoto</string> <string name="create_local_repo">Crea repo locale</string> - <string name="select_gpg_key_title">Seleziona\nChiave\nGPG</string> <string name="select_gpg_key_message">Seleziona una chiave GPG con cui inizializzare il tuo archivio</string> <string name="gpg_key_select">Seleziona chiave</string> <!-- SSH port validation --> @@ -315,7 +311,6 @@ <string name="invalid_gpg_id">Trovato .gpg-id, ma contiene un ID chiave, un\'impronta o un ID utente non valido</string> <string name="short_gpg_id">Trovato .gpg-id, ma contiene un ID esadecimale corto, che non è supportato</string> <string name="git_pull_merge_fail_error">Merge non riuscito, il repository è in conflitto.</string> - <string name="gpg_key_select_mandatory">Per proseguire devi selezionare una chiave GPG</string> <string name="git_scheme_disallowed_title">Il protocollo git:// è sconsigliato</string> <string name="git_scheme_disallowed_message">Il protocollo git di git-daemon non cifra il trasporto e non è adatto a operazioni sicure.</string> <string name="activity_proxy_label">Impostazioni proxy</string> diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 3efbed68..aaa438dd 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -167,7 +167,6 @@ <string name="show_password_pref_title">비밀번호 표시하기</string> <string name="show_password_pref_summary">암호가 해독된 후 비밀번호의 공개 여부를 제어합니다. 그렇다고 해서 클립보드 복사가 비활성화되지는 않습니다.</string> <string name="send_plaintext_password_to">비밀번호를 일반 텍스트로 보내는 중…</string> - <string name="app_icon_hint">앱 아이콘</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">입력할 항목 선택</string> <string name="oreo_autofill_strict_domain_search">피싱 방지 검색</string> @@ -281,16 +280,12 @@ username@example.com:…</string> <string name="git_break_out_of_detached_success">리베이스하는 동안 충돌이 발생했습니다. 로컬 %1$s 브랜치가 %2$s이라는 다른 브랜치로 푸시되었습니다.\n이 브랜치를 사용하여 컴퓨터에서 충돌을 해결하세요.</string> <string name="git_break_out_of_detached_unneeded">이 저장소는 리베이스가 아니므로 다른 브랜치로 푸시할 필요가 없습니다.</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">계속하려면 GPG 키를 선택해야 합니다</string> <string name="folder_creation_err_file_exists">동일한 이름의 파일이 이미 있습니다</string> <string name="folder_creation_err_folder_exists">이 이름의 폴더가 이미 존재합니다</string> <!-- Onboarding flow --> - <string name="let_s_go">시작합니다!</string> - <string name="select_n_repository_type">저장소 유형 \n선택</string> <string name="select_repo_type_text">로컬 리포지토리를 만들 것인지, 원격 리포지토리를 복제할 것인지 선택합니다.</string> <string name="clone_remote_repo">원격 저장소 복제</string> <string name="create_local_repo">로컬 저장소 생성</string> - <string name="select_gpg_key_title">GPG 키\n선택</string> <string name="select_gpg_key_message">스토어를 초기화할 GPG 키를 선택합니다</string> <string name="gpg_key_select">키 선택</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 65f14ce5..815cf41c 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -168,7 +168,6 @@ <string name="show_password_pref_title">Pokaż hasło</string> <string name="show_password_pref_summary">Kontroluj widoczność haseł po odszyfrowaniu. To nie wyłącza kopiowania do schowka.</string> <string name="send_plaintext_password_to">Wyślij niezaszyfrowane hasło, używając…</string> - <string name="app_icon_hint">Ikona aplikacji</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Wybierz wpis do wypełnienia</string> <string name="oreo_autofill_strict_domain_search">Wyszukiwanie odporne na wyłudzenie</string> @@ -278,12 +277,9 @@ <string name="folder_creation_err_file_exists">Plik o tej nazwie już istnieje</string> <string name="folder_creation_err_folder_exists">Folder o tej nazwie już istnieje</string> <!-- Onboarding flow --> - <string name="let_s_go">Zaczynajmy</string> - <string name="select_n_repository_type">Wybierz \ntyp repozytorium</string> <string name="select_repo_type_text">Zaznacz, czy chcesz utworzyć lokalne repozytorium, czy sklonować zdalne repozytorium.</string> <string name="clone_remote_repo">Klonuj zdalne repozytorium</string> <string name="create_local_repo">Utwórz lokalne repozytorium</string> - <string name="select_gpg_key_title">Wybierz\nklucz GPG</string> <string name="select_gpg_key_message">Wybierz klucz GPG, którym zainicjować swoje repozytorium</string> <string name="gpg_key_select">Wybierz klucz</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 86ebfb24..a1d1a38c 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -171,7 +171,6 @@ <string name="show_password_pref_title">Mostrar a senha</string> <string name="show_password_pref_summary">Controle a visibilidade das senhas quando descriptografadas. Isso não desativa a cópia para a área de transferência.</string> <string name="send_plaintext_password_to">Enviar senha como texto simples usando…</string> - <string name="app_icon_hint">Ícone do aplicativo</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Selecione a entrada para preencher</string> <string name="oreo_autofill_strict_domain_search">Busca resistente a phishing</string> @@ -283,16 +282,12 @@ <string name="git_break_out_of_detached_success">Houve um conflito ao executar o rebase. Houve um push de branch %1$s local para outra branch chamada %2$s\n Use esta branch para resolver o conflito no seu computador</string> <string name="git_break_out_of_detached_unneeded">O repositório não está sendo rebased, não há necessidade de fazer push para outro branch</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">A seleção de uma chave GPG é necessária para prosseguir</string> <string name="folder_creation_err_file_exists">Já existe um arquivo com esse nome</string> <string name="folder_creation_err_folder_exists">Já existe uma pasta com esse nome</string> <!-- Onboarding flow --> - <string name="let_s_go">Vamos lá</string> - <string name="select_n_repository_type">Selecione \nTipo de Repositório</string> <string name="select_repo_type_text">Selecione se você quer criar um repositório local ou clonar um repositório remoto.</string> <string name="clone_remote_repo">Clonar repositório remoto</string> <string name="create_local_repo">Criar repositório local</string> - <string name="select_gpg_key_title">Selecione\nChave GPG</string> <string name="select_gpg_key_message">Selecione uma chave GPG para inicializar o armazenamento com</string> <string name="gpg_key_select">Selecione a chave</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8873b34f..d0934a4d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -164,7 +164,6 @@ <string name="show_password_pref_title">Показывать пароли</string> <string name="show_password_pref_summary">Видимость расшифрованных паролей, не влияет на копирование в буфер.</string> <string name="send_plaintext_password_to">Поделиться паролем в открытом виде с помощью</string> - <string name="app_icon_hint">Иконка приложения</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Выберите запись для заполнения</string> <string name="oreo_autofill_strict_domain_search">Устойчивый к фишингу поиск</string> @@ -275,16 +274,12 @@ <string name="git_break_out_of_detached_success">При попытке rebase\'а возник конфликт. Ваша локальная ветка %1$s была запушена %2$s\n Используйте эту ветку для разрешения конфликтов с помощью компьютера</string> <string name="git_break_out_of_detached_unneeded">Не нужно пушить в другую ветку - rebase не был запущен</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">Для продолжения необходимо выбрать GPG-ключ</string> <string name="folder_creation_err_file_exists">Файл с таким именем уже существует</string> <string name="folder_creation_err_folder_exists">Папка с таким именем уже существует</string> <!-- Onboarding flow --> - <string name="let_s_go">Поехали</string> - <string name="select_n_repository_type">Выберите \nтип репозитория</string> <string name="select_repo_type_text">Создать локальное хранилище, или клонировать удаленный репозиторий.</string> <string name="clone_remote_repo">Клонировать удаленный репозиторий</string> <string name="create_local_repo">Создать локальное хранилище</string> - <string name="select_gpg_key_title">Выберите\nGPG ключ</string> <string name="select_gpg_key_message">Выберите GPG ключ для инициализации хранилища</string> <string name="gpg_key_select">Выберите ключ</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 746b74d2..ddcdda1a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -172,7 +172,6 @@ <string name="show_password_pref_title">显示密码</string> <string name="show_password_pref_summary">控制密码解密后的可见性,这不会禁用复制到剪贴板</string> <string name="send_plaintext_password_to">使用明文发送密码…</string> - <string name="app_icon_hint">应用图标</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">选择要填写的条目</string> <string name="oreo_autofill_strict_domain_search">防钓鱼搜索</string> @@ -285,18 +284,12 @@ personal.com</string> 使用此分支解决计算机上的冲突</string> <string name="git_break_out_of_detached_unneeded">存储库没有变更,无需推送到另一个分支</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">选择一个 GPG 密钥才能继续</string> <string name="folder_creation_err_file_exists">该名称的文件已存在</string> <string name="folder_creation_err_folder_exists">该名称的文件夹已存在</string> <!-- Onboarding flow --> - <string name="let_s_go">开始配置</string> - <string name="select_n_repository_type">选择仓库模式</string> <string name="select_repo_type_text">你是要同步仓库还是现在创建一个仓库呢?</string> <string name="clone_remote_repo">同步远程仓库</string> <string name="create_local_repo">创建本地仓库</string> - <string name="select_gpg_key_title">选择 -GPG -密钥</string> <string name="select_gpg_key_message">选择一个GPG密钥来初始化您的仓库</string> <string name="gpg_key_select">选择密钥</string> <!-- SSH port validation --> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e5d7ff94..8351a358 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -7,9 +7,5 @@ <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> - <dimen name="fab_compat_margin">16dp</dimen> <dimen name="normal_margin">8dp</dimen> - <dimen name="onboarding_icon_margin_top">100dp</dimen> - <dimen name="onboarding_button_margin_top">140dp</dimen> - <dimen name="onboarding_desc_margin_top">48dp</dimen> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dc8069b8..59ba74cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -199,7 +199,6 @@ <string name="show_password_pref_title">Show the password</string> <string name="show_password_pref_summary">Control the visibility of the passwords once decrypted. This does not disable copying to clipboard.</string> <string name="send_plaintext_password_to">Send password as plaintext using…</string> - <string name="app_icon_hint">App icon</string> <!-- Oreo Autofill --> <string name="oreo_autofill_select_and_fill_into">Select entry to fill into</string> @@ -321,17 +320,13 @@ <string name="git_break_out_of_detached_unneeded">The repository is not rebasing, no need to push to another branch</string> <!-- GPG key selection in folder creation --> - <string name="gpg_key_select_mandatory">Selecting a GPG key is necessary to proceed</string> <string name="folder_creation_err_file_exists">A file by that name already exists</string> <string name="folder_creation_err_folder_exists">A folder by that name already exists</string> <!-- Onboarding flow --> - <string name="let_s_go">Let\'s Go</string> - <string name="select_n_repository_type">Select \nRepository Type</string> <string name="select_repo_type_text">Select if you want to create a local repo or clone a remote repo.</string> <string name="clone_remote_repo">Clone Remote Repo</string> <string name="create_local_repo">Create Local Repo</string> - <string name="select_gpg_key_title">Select\nGPG Key</string> <string name="select_gpg_key_message">Select a GPG key to initialize your store with</string> <string name="gpg_key_select">Select key</string> |
