aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-02 21:39:54 +0100
committerDanilo M. <danix@danix.xyz>2026-11-02 21:39:54 +0100
commitbcd2d061288004020cc437694dbf4218abc52c89 (patch)
tree7ee8561ba881d6a93f4629e2a30a0d3c68235c2b /app/src/main
parent815f18752f99fb8cc7f57401eafaef20d9c59ca5 (diff)
downloadHardenedPass-bcd2d061288004020cc437694dbf4218abc52c89.tar.gz
HardenedPass-bcd2d061288004020cc437694dbf4218abc52c89.zip
refactor(app): remove the legacy PasswordStore list
The Compose list replaces it. The autofill inline suggestion's attribution intent now opens LaunchActivity, behind the biometric gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/AndroidManifest.xml5
-rw-r--r--app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt77
-rw-r--r--app/src/main/java/app/passwordstore/ui/dialogs/ItemCreationBottomSheet.kt77
-rw-r--r--app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderActivity.kt18
-rw-r--r--app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt12
-rw-r--r--app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt388
-rw-r--r--app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt621
-rw-r--r--app/src/main/java/app/passwordstore/ui/util/OnOffItemAnimator.kt71
-rw-r--r--app/src/main/java/app/passwordstore/util/autofill/AutofillViewUtils.kt4
-rw-r--r--app/src/main/res/anim/scale_down.xml16
-rw-r--r--app/src/main/res/anim/scale_up.xml16
-rw-r--r--app/src/main/res/layout/activity_pwdstore.xml19
-rw-r--r--app/src/main/res/layout/folder_dialog_fragment.xml38
-rw-r--r--app/src/main/res/layout/item_create_sheet.xml42
-rw-r--r--app/src/main/res/layout/password_recycler_view.xml3
-rw-r--r--app/src/main/res/menu/context_pass.xml34
-rw-r--r--app/src/main/res/menu/main_menu_git.xml36
-rw-r--r--app/src/main/res/menu/main_menu_no_auth.xml28
-rw-r--r--app/src/main/res/menu/main_menu_non_git.xml24
-rw-r--r--app/src/main/res/values-de/strings.xml5
-rw-r--r--app/src/main/res/values-fr/strings.xml5
-rw-r--r--app/src/main/res/values-gl/strings.xml5
-rw-r--r--app/src/main/res/values-it/strings.xml5
-rw-r--r--app/src/main/res/values-ko-rKR/strings.xml5
-rw-r--r--app/src/main/res/values-pl-rPL/strings.xml5
-rw-r--r--app/src/main/res/values-pt-rBR/strings.xml5
-rw-r--r--app/src/main/res/values-ru/strings.xml5
-rw-r--r--app/src/main/res/values-zh-rCN/strings.xml5
-rw-r--r--app/src/main/res/values/dimens.xml1
-rw-r--r--app/src/main/res/values/strings.xml5
30 files changed, 10 insertions, 1570 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4840ca1f..9bfb3b6a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,11 +34,6 @@
tools:targetApi="s">
<activity
- android:name=".ui.passwords.PasswordStore"
- android:configChanges="orientation|screenSize"
- android:exported="false" />
-
- <activity
android:name=".ui.onboarding.activity.OnboardingActivity"
android:configChanges="orientation|screenSize"
android:exported="false" />
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt
deleted file mode 100644
index 669e126e..00000000
--- a/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.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.dialogs
-
-import android.app.Dialog
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.appcompat.app.AlertDialog
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import app.passwordstore.R
-import app.passwordstore.ui.passwords.PasswordStore
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.textfield.TextInputEditText
-import com.google.android.material.textfield.TextInputLayout
-import java.io.File
-
-class FolderCreationDialogFragment : DialogFragment() {
-
- private lateinit var newFolder: File
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
- alertDialogBuilder.setTitle(R.string.title_create_folder)
- alertDialogBuilder.setView(R.layout.folder_dialog_fragment)
- alertDialogBuilder.setPositiveButton(getString(R.string.button_create), null)
- alertDialogBuilder.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
- val dialog = alertDialogBuilder.create()
- dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- dialog.setOnShowListener {
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
- createDirectory(requireArguments().getString(CURRENT_DIR_EXTRA)!!)
- }
- }
- return dialog
- }
-
- private fun createDirectory(currentDir: String) {
- val dialog = requireDialog()
- val folderNameView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text)
- val folderNameViewContainer = dialog.findViewById<TextInputLayout>(R.id.folder_name_container)
- newFolder = File("$currentDir/${folderNameView.text}")
- folderNameViewContainer.error =
- when {
- newFolder.isFile -> getString(R.string.folder_creation_err_file_exists)
- newFolder.isDirectory -> getString(R.string.folder_creation_err_folder_exists)
- else -> null
- }
- if (folderNameViewContainer.error != null) return
- newFolder.mkdirs()
- (requireActivity() as PasswordStore).refreshPasswordList(newFolder)
- // TODO(msfjarvis): Restore this functionality
- /*
- if (dialog.findViewById<MaterialCheckBox>(R.id.set_gpg_key).isChecked) {
- keySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
- return
- } else {
- dismiss()
- }
- */
- dismiss()
- }
-
- companion object {
-
- private const val CURRENT_DIR_EXTRA = "CURRENT_DIRECTORY"
-
- fun newInstance(startingDirectory: String): FolderCreationDialogFragment {
- val extras = bundleOf(CURRENT_DIR_EXTRA to startingDirectory)
- val fragment = FolderCreationDialogFragment()
- fragment.arguments = extras
- return fragment
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/ItemCreationBottomSheet.kt b/app/src/main/java/app/passwordstore/ui/dialogs/ItemCreationBottomSheet.kt
deleted file mode 100644
index 0304b16c..00000000
--- a/app/src/main/java/app/passwordstore/ui/dialogs/ItemCreationBottomSheet.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.dialogs
-
-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.core.os.bundleOf
-import androidx.fragment.app.setFragmentResult
-import app.passwordstore.R
-import app.passwordstore.ui.passwords.PasswordFragment.Companion.ACTION_FOLDER
-import app.passwordstore.ui.passwords.PasswordFragment.Companion.ACTION_KEY
-import app.passwordstore.ui.passwords.PasswordFragment.Companion.ACTION_PASSWORD
-import app.passwordstore.ui.passwords.PasswordFragment.Companion.ITEM_CREATION_REQUEST_KEY
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetDialog
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-
-class ItemCreationBottomSheet : BottomSheetDialogFragment() {
-
- 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 inflater.inflate(R.layout.item_create_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)
- }
- dialog.findViewById<View>(R.id.create_folder)?.setOnClickListener {
- setFragmentResult(ITEM_CREATION_REQUEST_KEY, bundleOf(ACTION_KEY to ACTION_FOLDER))
- dismiss()
- }
- dialog.findViewById<View>(R.id.create_password)?.setOnClickListener {
- setFragmentResult(ITEM_CREATION_REQUEST_KEY, bundleOf(ACTION_KEY to ACTION_PASSWORD))
- dismiss()
- }
- }
- }
- )
- }
-
- override fun dismiss() {
- super.dismiss()
- behavior?.removeBottomSheetCallback(bottomSheetCallback)
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderActivity.kt b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderActivity.kt
index cfeb6206..2139d7d2 100644
--- a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderActivity.kt
@@ -11,9 +11,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commit
import app.passwordstore.R
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.passwords.PASSWORD_FRAGMENT_TAG
-import app.passwordstore.ui.passwords.PasswordStore
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -23,23 +20,10 @@ class SelectFolderActivity : AppCompatActivity(R.layout.select_folder_layout) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
passwordList = SelectFolderFragment()
- val args = Bundle()
- args.putString(
- PasswordStore.REQUEST_ARG_PATH,
- PasswordRepository.getRepositoryDirectory().absolutePath,
- )
-
- passwordList.arguments = args
-
supportActionBar?.show()
-
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
-
- supportFragmentManager.commit {
- replace(R.id.pgp_handler_linearlayout, passwordList, PASSWORD_FRAGMENT_TAG)
- }
+ supportFragmentManager.commit { replace(R.id.pgp_handler_linearlayout, passwordList) }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
diff --git a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt
index 3cf12fdd..acf4b56d 100644
--- a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt
@@ -15,9 +15,9 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import app.passwordstore.R
import app.passwordstore.data.password.PasswordItem
+import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.databinding.PasswordRecyclerViewBinding
import app.passwordstore.ui.adapters.PasswordItemRecyclerAdapter
-import app.passwordstore.ui.passwords.PasswordStore
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.viewBinding
import app.passwordstore.util.viewmodel.ListMode
@@ -56,11 +56,11 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) {
FastScrollerBuilder(binding.passRecycler).build()
registerForContextMenu(binding.passRecycler)
- val path =
- requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) {
- "Cannot navigate if ${PasswordStore.REQUEST_ARG_PATH} is not provided"
- }
- model.navigateTo(File(path), listMode = ListMode.DirectoriesOnly, pushPreviousLocation = false)
+ model.navigateTo(
+ PasswordRepository.getRepositoryDirectory(),
+ listMode = ListMode.DirectoriesOnly,
+ pushPreviousLocation = false,
+ )
lifecycleScope.launch {
model.searchResult.flowWithLifecycle(lifecycle).collect { result ->
recyclerAdapter.submitList(result.passwordItems)
diff --git a/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt b/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt
deleted file mode 100644
index 917bb91c..00000000
--- a/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.ui.passwords
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Bundle
-import android.os.Parcelable
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.view.animation.Animation
-import android.view.animation.AnimationUtils
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.appcompat.view.ActionMode
-import androidx.core.content.edit
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.flowWithLifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import app.passwordstore.R
-import app.passwordstore.data.password.PasswordItem
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.databinding.PasswordRecyclerViewBinding
-import app.passwordstore.injection.prefs.SettingsPreferences
-import app.passwordstore.ui.adapters.PasswordItemRecyclerAdapter
-import app.passwordstore.ui.dialogs.BasicBottomSheet
-import app.passwordstore.ui.dialogs.ItemCreationBottomSheet
-import app.passwordstore.ui.git.base.BaseGitActivity
-import app.passwordstore.ui.git.config.GitServerConfigActivity
-import app.passwordstore.ui.util.OnOffItemAnimator
-import app.passwordstore.util.coroutines.DispatcherProvider
-import app.passwordstore.util.extensions.base64
-import app.passwordstore.util.extensions.getString
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.settings.AuthMode
-import app.passwordstore.util.settings.GitSettings
-import app.passwordstore.util.settings.PasswordSortOrder
-import app.passwordstore.util.settings.PreferenceKeys
-import app.passwordstore.util.shortcuts.ShortcutHandler
-import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel
-import com.github.michaelbull.result.fold
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.File
-import javax.inject.Inject
-import kotlinx.coroutines.launch
-import me.zhanghai.android.fastscroll.FastScrollerBuilder
-
-@AndroidEntryPoint
-class PasswordFragment : Fragment(R.layout.password_recycler_view) {
-
- @Inject lateinit var gitSettings: GitSettings
- @Inject lateinit var shortcutHandler: ShortcutHandler
- @Inject lateinit var dispatcherProvider: DispatcherProvider
- @Inject @SettingsPreferences lateinit var prefs: SharedPreferences
- private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter
- private lateinit var listener: OnFragmentInteractionListener
- private lateinit var settings: SharedPreferences
-
- private var recyclerViewStateToRestore: Parcelable? = null
- private var actionMode: ActionMode? = null
- private var scrollTarget: File? = null
-
- private val model: SearchableRepositoryViewModel by activityViewModels()
- private val binding by viewBinding(PasswordRecyclerViewBinding::bind)
- private val swipeResult =
- registerForActivityResult(StartActivityForResult()) {
- binding.swipeRefresher.isRefreshing = false
- requireStore().refreshPasswordList()
- }
-
- val currentDir: File
- get() = model.currentDir.value
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- settings = requireContext().sharedPrefs
- initializePasswordList()
- binding.fab.setOnClickListener {
- ItemCreationBottomSheet().show(childFragmentManager, "BOTTOM_SHEET")
- }
- childFragmentManager.setFragmentResultListener(ITEM_CREATION_REQUEST_KEY, viewLifecycleOwner) {
- _,
- bundle ->
- when (bundle.getString(ACTION_KEY)) {
- ACTION_FOLDER -> requireStore().createFolder()
- ACTION_PASSWORD -> requireStore().createPassword()
- }
- }
- }
-
- private fun initializePasswordList() {
- val gitDir = File(PasswordRepository.getRepositoryDirectory(), ".git")
- val hasGitDir =
- gitDir.exists() && gitDir.isDirectory && (gitDir.listFiles()?.isNotEmpty() == true)
- binding.swipeRefresher.setOnRefreshListener {
- if (!hasGitDir) {
- requireStore().refreshPasswordList()
- binding.swipeRefresher.isRefreshing = false
- } else if (!PasswordRepository.isGitRepo()) {
- BasicBottomSheet.Builder(requireContext())
- .setMessageRes(R.string.clone_git_repo)
- .setPositiveButtonClickListener(getString(R.string.clone_button)) {
- swipeResult.launch(GitServerConfigActivity.createCloneIntent(requireContext()))
- }
- .build()
- .show(requireActivity().supportFragmentManager, "NOT_A_GIT_REPO")
- binding.swipeRefresher.isRefreshing = false
- } else {
- // When authentication is set to AuthMode.None then the only git operation we can
- // run is a pull, so automatically fallback to that.
- val operationId =
- when (gitSettings.authMode) {
- AuthMode.None -> BaseGitActivity.GitOp.PULL
- else -> BaseGitActivity.GitOp.SYNC
- }
- requireStore().apply {
- lifecycleScope.launch {
- launchGitOperation(operationId)
- .fold(
- success = {
- binding.swipeRefresher.isRefreshing = false
- refreshPasswordList()
- },
- failure = { err ->
- promptOnErrorHandler(err) { binding.swipeRefresher.isRefreshing = false }
- },
- )
- }
- }
- }
- }
-
- recyclerAdapter =
- PasswordItemRecyclerAdapter(lifecycleScope, dispatcherProvider)
- .onItemClicked { _, item -> listener.onFragmentInteraction(item) }
- .onSelectionChanged { selection ->
- // In order to not interfere with drag selection, we disable the
- // SwipeRefreshLayout
- // once an item is selected.
- binding.swipeRefresher.isEnabled = selection.isEmpty
-
- if (actionMode == null)
- actionMode =
- requireStore().startSupportActionMode(actionModeCallback) ?: return@onSelectionChanged
-
- if (!selection.isEmpty) {
- actionMode!!.title =
- resources.getQuantityString(
- R.plurals.delete_title,
- selection.size(),
- selection.size(),
- )
- actionMode!!.invalidate()
- } else {
- actionMode!!.finish()
- }
- }
- val recyclerView = binding.passRecycler
- recyclerView.apply {
- addItemDecoration(DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL))
- layoutManager = LinearLayoutManager(requireContext())
- itemAnimator = OnOffItemAnimator()
- adapter = recyclerAdapter
- }
-
- FastScrollerBuilder(recyclerView).build()
- recyclerAdapter.makeSelectable(recyclerView)
- registerForContextMenu(recyclerView)
-
- val path =
- requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) {
- "Cannot navigate if ${PasswordStore.REQUEST_ARG_PATH} is not provided"
- }
- model.navigateTo(File(path), pushPreviousLocation = false)
- lifecycleScope.launch {
- model.searchResult.flowWithLifecycle(lifecycle).collect { result ->
- // Only run animations when the new list is filtered, i.e., the user submitted a search,
- // and not on folder navigation since the latter leads to too many removal animations.
- (recyclerView.itemAnimator as OnOffItemAnimator).isEnabled = result.isFiltered
- recyclerAdapter.submitList(result.passwordItems) {
- when {
- result.isFiltered -> {
- // When the result is filtered, we always scroll to the top since that is
- // where the best fuzzy match appears.
- recyclerView.scrollToPosition(0)
- }
- scrollTarget != null -> {
- scrollTarget?.let {
- recyclerView.scrollToPosition(recyclerAdapter.getPositionForFile(it))
- }
- scrollTarget = null
- }
- else -> {
- // When the result is not filtered and there is a saved scroll position for
- // it, we try to restore it.
- recyclerViewStateToRestore?.let {
- recyclerView.layoutManager!!.onRestoreInstanceState(it)
- }
- recyclerViewStateToRestore = null
- }
- }
- }
- }
- }
- }
-
- private val actionModeCallback =
- object : ActionMode.Callback {
- // Called when the action mode is created; startActionMode() was called
- override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
- // Inflate a menu resource providing context menu items
- mode.menuInflater.inflate(R.menu.context_pass, menu)
- // hide the fab
- animateFab(false)
- return true
- }
-
- // Called each time the action mode is shown. Always called after onCreateActionMode,
- // but may be called multiple times if the mode is invalidated.
- override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
- val selectedItems = recyclerAdapter.getSelectedItems()
- menu.findItem(R.id.menu_edit_password).isVisible =
- selectedItems.all { it.type == PasswordItem.TYPE_CATEGORY }
- menu.findItem(R.id.menu_pin_password).isVisible =
- selectedItems.size == 1 && selectedItems[0].type == PasswordItem.TYPE_PASSWORD
- return true
- }
-
- // Called when the user selects a contextual menu item
- override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.menu_delete_password -> {
- requireStore().deletePasswords(recyclerAdapter.getSelectedItems())
- // Action picked, so close the CAB
- mode.finish()
- true
- }
- R.id.menu_move_password -> {
- requireStore().movePasswords(recyclerAdapter.getSelectedItems())
- false
- }
- R.id.menu_edit_password -> {
- requireStore().renameCategory(recyclerAdapter.getSelectedItems())
- mode.finish()
- false
- }
- R.id.menu_pin_password -> {
- val passwordItem = recyclerAdapter.getSelectedItems()[0]
- shortcutHandler.addPinnedShortcut(
- passwordItem,
- passwordItem.createAuthEnabledIntent(requireContext()),
- )
- false
- }
- else -> false
- }
- }
-
- // Called when the user exits the action mode
- override fun onDestroyActionMode(mode: ActionMode) {
- recyclerAdapter.requireSelectionTracker().clearSelection()
- actionMode = null
- // show the fab
- animateFab(true)
- }
-
- private fun animateFab(show: Boolean) =
- with(binding.fab) {
- val animation =
- AnimationUtils.loadAnimation(context, if (show) R.anim.scale_up else R.anim.scale_down)
- animation.setAnimationListener(
- object : Animation.AnimationListener {
- override fun onAnimationRepeat(animation: Animation?) {}
-
- override fun onAnimationEnd(animation: Animation?) {
- if (!show) visibility = View.GONE
- }
-
- override fun onAnimationStart(animation: Animation?) {
- if (show) visibility = View.VISIBLE
- }
- }
- )
- animate()
- .rotationBy(if (show) -90f else 90f)
- .setStartDelay(if (show) 100 else 0)
- .setDuration(100)
- .start()
- startAnimation(animation)
- }
- }
-
- override fun onResume() {
- super.onResume()
- binding.swipeRefresher.isEnabled = !prefs.getBoolean(PreferenceKeys.DISABLE_SYNC_ACTION, false)
- }
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
- runCatching {
- listener =
- object : OnFragmentInteractionListener {
- override fun onFragmentInteraction(item: PasswordItem) {
- if (
- settings.getString(PreferenceKeys.SORT_ORDER) ==
- PasswordSortOrder.RECENTLY_USED.name
- ) {
- // save the time when password was used
- val preferences =
- context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
- preferences.edit {
- putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString())
- }
- }
-
- if (item.type == PasswordItem.TYPE_CATEGORY) {
- navigateTo(item.file)
- } else {
- if (requireArguments().getBoolean("matchWith", false)) {
- requireStore().matchPasswordWithApp(item)
- } else {
- requireStore().decryptPassword(item)
- }
- }
- }
- }
- }
- .onFailure {
- throw ClassCastException("$context must implement OnFragmentInteractionListener")
- }
- }
-
- private fun requireStore() = requireActivity() as PasswordStore
-
- /** Returns true if the back press was handled by the [Fragment]. */
- fun onBackPressedInActivity(): Boolean {
- if (!model.canNavigateBack) return false
- // The RecyclerView state is restored when the asynchronous update operation on the
- // adapter is completed.
- recyclerViewStateToRestore = model.navigateBack()
- if (!model.canNavigateBack) requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(false)
- return true
- }
-
- fun dismissActionMode() {
- actionMode?.finish()
- }
-
- companion object {
-
- const val ITEM_CREATION_REQUEST_KEY = "creation_key"
- const val ACTION_KEY = "action"
- const val ACTION_FOLDER = "folder"
- const val ACTION_PASSWORD = "password"
-
- fun newInstance(args: Bundle): PasswordFragment {
- val fragment = PasswordFragment()
- fragment.arguments = args
- return fragment
- }
- }
-
- fun navigateTo(file: File) {
- requireStore().clearSearch()
- model.navigateTo(
- file,
- recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState(),
- )
- requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(true)
- }
-
- fun scrollToOnNextRefresh(file: File) {
- scrollTarget = file
- }
-
- interface OnFragmentInteractionListener {
-
- fun onFragmentInteraction(item: PasswordItem)
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt b/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt
deleted file mode 100644
index 059b398f..00000000
--- a/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.ui.passwords
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.view.KeyEvent
-import android.view.Menu
-import android.view.MenuItem
-import android.view.MenuItem.OnActionExpandListener
-import android.view.WindowManager
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.activity.viewModels
-import androidx.appcompat.widget.SearchView
-import androidx.appcompat.widget.SearchView.OnQueryTextListener
-import androidx.core.content.edit
-import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.commit
-import androidx.lifecycle.flowWithLifecycle
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.R
-import app.passwordstore.data.password.PasswordItem
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.crypto.BasePGPActivity
-import app.passwordstore.ui.crypto.BasePGPActivity.Companion.getLongName
-import app.passwordstore.ui.crypto.DecryptActivity
-import app.passwordstore.ui.crypto.PasswordCreationActivity
-import app.passwordstore.ui.dialogs.FolderCreationDialogFragment
-import app.passwordstore.ui.folderselect.SelectFolderActivity
-import app.passwordstore.ui.git.base.BaseGitActivity
-import app.passwordstore.ui.onboarding.activity.OnboardingActivity
-import app.passwordstore.ui.settings.SettingsActivity
-import app.passwordstore.util.autofill.AutofillMatcher
-import app.passwordstore.util.extensions.base64
-import app.passwordstore.util.extensions.commitChange
-import app.passwordstore.util.extensions.contains
-import app.passwordstore.util.extensions.getString
-import app.passwordstore.util.extensions.isInsideRepository
-import app.passwordstore.util.extensions.launchActivity
-import app.passwordstore.util.extensions.listFilesRecursively
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.settings.AuthMode
-import app.passwordstore.util.settings.PreferenceKeys
-import app.passwordstore.util.shortcuts.ShortcutHandler
-import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel
-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.textfield.TextInputEditText
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.File
-import java.lang.Character.UnicodeBlock
-import javax.inject.Inject
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.LogPriority.INFO
-import logcat.logcat
-
-const val PASSWORD_FRAGMENT_TAG = "PasswordsList"
-
-@AndroidEntryPoint
-class PasswordStore : BaseGitActivity() {
-
- @Inject lateinit var shortcutHandler: ShortcutHandler
- private lateinit var searchItem: MenuItem
- private val settings by lazy { sharedPrefs }
-
- private val model: SearchableRepositoryViewModel by viewModels()
-
- private val listRefreshAction =
- registerForActivityResult(StartActivityForResult()) { result ->
- if (result.resultCode == RESULT_OK) {
- refreshPasswordList()
- }
- }
-
- private val passwordMoveAction =
- registerForActivityResult(StartActivityForResult()) { result ->
- val intentData = result.data ?: return@registerForActivityResult
- val filesToMove =
- requireNotNull(intentData.getStringArrayExtra("Files")) {
- "'Files' intent extra must be set"
- }
- val target =
- File(
- requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH")) {
- "'SELECTED_FOLDER_PATH' intent extra must be set"
- }
- )
- val repositoryPath = PasswordRepository.getRepositoryDirectory().absolutePath
- if (!target.isDirectory) {
- logcat(ERROR) { "Tried moving passwords to a non-existing folder." }
- return@registerForActivityResult
- }
-
- logcat { "Moving passwords to ${intentData.getStringExtra("SELECTED_FOLDER_PATH")}" }
- logcat { filesToMove.joinToString(", ") }
-
- lifecycleScope.launch(dispatcherProvider.io()) {
- for (file in filesToMove) {
- val source = File(file)
- if (!source.exists()) {
- logcat(ERROR) { "Tried moving something that appears non-existent." }
- continue
- }
- val destinationFile = File(target.absolutePath + "/" + source.name)
- val basename = source.nameWithoutExtension
- val sourceLongName =
- getLongName(
- requireNotNull(source.parent) { "$file has no parent" },
- repositoryPath,
- basename,
- )
- val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
- if (destinationFile.exists()) {
- logcat(ERROR) { "Trying to move a file that already exists." }
- withContext(dispatcherProvider.main()) {
- MaterialAlertDialogBuilder(this@PasswordStore)
- .setTitle(resources.getString(R.string.password_exists_title))
- .setMessage(
- resources.getString(
- R.string.password_exists_message,
- destinationLongName,
- sourceLongName,
- )
- )
- .setPositiveButton(R.string.dialog_ok) { _, _ ->
- launch(dispatcherProvider.io()) { moveFile(source, destinationFile) }
- }
- .setNegativeButton(R.string.dialog_cancel, null)
- .show()
- }
- } else {
- launch(dispatcherProvider.io()) { moveFile(source, destinationFile) }
- }
- }
- when (filesToMove.size) {
- 1 -> {
- val source = File(filesToMove[0])
- val basename = source.nameWithoutExtension
- val sourceLongName =
- getLongName(
- requireNotNull(source.parent) { "$basename has no parent" },
- repositoryPath,
- basename,
- )
- val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
- withContext(dispatcherProvider.main()) {
- commitChange(
- resources.getString(
- R.string.git_commit_move_text,
- sourceLongName,
- destinationLongName,
- )
- )
- }
- }
- else -> {
- val repoDir = PasswordRepository.getRepositoryDirectory().absolutePath
- val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
- withContext(dispatcherProvider.main()) {
- commitChange(
- resources.getString(R.string.git_commit_move_multiple_text, relativePath)
- )
- }
- }
- }
- }
- refreshPasswordList()
- getPasswordFragment()?.dismissActionMode()
- }
-
- override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
- // open search view on search key, or Ctr+F
- if (
- (keyCode == KeyEvent.KEYCODE_SEARCH ||
- keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && !searchItem.isActionViewExpanded
- ) {
- searchItem.expandActionView()
- return true
- }
-
- // open search view on any printable character and query for it
- val c = event.unicodeChar.toChar()
- val printable = isPrintable(c)
- if (printable && !searchItem.isActionViewExpanded) {
- searchItem.expandActionView()
- (searchItem.actionView as SearchView).setQuery(c.toString(), true)
- return true
- }
- return super.onKeyDown(keyCode, event)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_pwdstore)
-
- lifecycleScope.launch {
- model.currentDir.flowWithLifecycle(lifecycle).collect { dir ->
- val basePath = PasswordRepository.getRepositoryDirectory().absoluteFile
- supportActionBar?.apply {
- if (dir != basePath) title = dir.name else setTitle(R.string.app_name)
- }
- }
- }
- }
-
- override fun onStart() {
- super.onStart()
- refreshPasswordList()
- }
-
- override fun onResume() {
- super.onResume()
- checkLocalRepository()
- if (settings.getBoolean(PreferenceKeys.SEARCH_ON_START, false) && ::searchItem.isInitialized) {
- if (!searchItem.isActionViewExpanded) {
- searchItem.expandActionView()
- }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- val menuRes =
- when {
- gitSettings.authMode == AuthMode.None -> R.menu.main_menu_no_auth
- PasswordRepository.isGitRepo() -> R.menu.main_menu_git
- else -> R.menu.main_menu_non_git
- }
- menuInflater.inflate(menuRes, menu)
- return super.onCreateOptionsMenu(menu)
- }
-
- override fun onPrepareOptionsMenu(menu: Menu): Boolean {
- // Invalidation forces onCreateOptionsMenu to be called again. This is cheap and quick so
- // we can get by without any noticeable difference in performance.
- invalidateOptionsMenu()
- searchItem = menu.findItem(R.id.action_search)
- val searchView = searchItem.actionView as SearchView
- searchView.setOnQueryTextListener(
- object : OnQueryTextListener {
- override fun onQueryTextSubmit(s: String): Boolean {
- searchView.clearFocus()
- return true
- }
-
- override fun onQueryTextChange(s: String): Boolean {
- val filter = s.trim()
- // List the contents of the current directory if the user enters a blank
- // search term.
- if (filter.isEmpty())
- model.navigateTo(newDirectory = model.currentDir.value, pushPreviousLocation = false)
- else model.search(filter)
- return true
- }
- }
- )
-
- // When using the support library, the setOnActionExpandListener() method is
- // static and accepts the MenuItem object as an argument
- searchItem.setOnActionExpandListener(
- object : OnActionExpandListener {
- override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
- refreshPasswordList()
- return true
- }
-
- override fun onMenuItemActionExpand(item: MenuItem): Boolean {
- return true
- }
- }
- )
- if (settings.getBoolean(PreferenceKeys.SEARCH_ON_START, false)) {
- searchItem.expandActionView()
- }
- return super.onPrepareOptionsMenu(menu)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- val id = item.itemId
- val initBefore =
- MaterialAlertDialogBuilder(this)
- .setMessage(resources.getString(R.string.creation_dialog_text))
- .setPositiveButton(resources.getString(R.string.dialog_ok), null)
- when (id) {
- R.id.user_pref -> {
- runCatching { launchActivity(SettingsActivity::class.java) }
- .onFailure { e -> e.printStackTrace() }
- }
- R.id.git_push -> {
- if (!PasswordRepository.isInitialized) {
- initBefore.show()
- } else {
- runGitOperation(GitOp.PUSH)
- }
- }
- R.id.git_pull -> {
- if (!PasswordRepository.isInitialized) {
- initBefore.show()
- } else {
- runGitOperation(GitOp.PULL)
- }
- }
- R.id.git_sync -> {
- if (!PasswordRepository.isInitialized) {
- initBefore.show()
- } else {
- runGitOperation(GitOp.SYNC)
- }
- }
- R.id.refresh -> refreshPasswordList()
- android.R.id.home -> {
- @Suppress("DEPRECATION") onBackPressed()
- }
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- @Deprecated("Deprecated in Java")
- @Suppress("DEPRECATION")
- override fun onBackPressed() {
- if (getPasswordFragment()?.onBackPressedInActivity() != true) super.onBackPressed()
- }
-
- private fun getPasswordFragment(): PasswordFragment? {
- return supportFragmentManager.findFragmentByTag(PASSWORD_FRAGMENT_TAG) as? PasswordFragment
- }
-
- fun clearSearch() {
- if (searchItem.isActionViewExpanded) searchItem.collapseActionView()
- }
-
- private fun runGitOperation(operation: GitOp) =
- lifecycleScope.launch {
- launchGitOperation(operation)
- .fold(success = { refreshPasswordList() }, failure = { promptOnErrorHandler(it) })
- }
-
- private fun checkLocalRepository() {
- PasswordRepository.initialize()
- checkLocalRepository(PasswordRepository.getRepositoryDirectory())
- }
-
- private fun checkLocalRepository(localDir: File?) {
- if (localDir != null && settings.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)) {
- // do not push the fragment if we already have it
- if (
- getPasswordFragment() == null || settings.getBoolean(PreferenceKeys.REPO_CHANGED, false)
- ) {
- settings.edit { putBoolean(PreferenceKeys.REPO_CHANGED, false) }
- val args = Bundle()
- args.putString(REQUEST_ARG_PATH, PasswordRepository.getRepositoryDirectory().absolutePath)
-
- // if the activity was started from the autofill settings, the
- // intent is to match a clicked pwd with app. pass this to fragment
- if (intent.getBooleanExtra("matchWith", false)) {
- args.putBoolean("matchWith", true)
- }
- supportActionBar?.apply {
- show()
- setDisplayHomeAsUpEnabled(false)
- }
- supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
- supportFragmentManager.commit {
- replace(R.id.main_layout, PasswordFragment.newInstance(args), PASSWORD_FRAGMENT_TAG)
- }
- }
- } else {
- launchActivity(OnboardingActivity::class.java)
- }
- }
-
- private fun getRelativePath(fullPath: String, repositoryPath: String): String {
- return fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
- }
-
- fun decryptPassword(item: PasswordItem) {
- val authDecryptIntent = item.createAuthEnabledIntent(this)
- val decryptIntent =
- Intent(authDecryptIntent).setComponent(ComponentName(this, DecryptActivity::class.java))
-
- startActivity(decryptIntent)
-
- // Adds shortcut
- shortcutHandler.addDynamicShortcut(item, authDecryptIntent)
- }
-
- private fun validateState(): Boolean {
- if (!PasswordRepository.isInitialized) {
- MaterialAlertDialogBuilder(this)
- .setMessage(resources.getString(R.string.creation_dialog_text))
- .setPositiveButton(resources.getString(R.string.dialog_ok), null)
- .show()
- return false
- }
- return true
- }
-
- fun createPassword() {
- if (!validateState()) return
- val currentDir = currentDir
- logcat(INFO) { "Adding file to : ${currentDir.absolutePath}" }
- val intent = Intent(this, PasswordCreationActivity::class.java)
- intent.putExtra(BasePGPActivity.EXTRA_FILE_PATH, currentDir.absolutePath)
- intent.putExtra(
- BasePGPActivity.EXTRA_REPO_PATH,
- PasswordRepository.getRepositoryDirectory().absolutePath,
- )
- listRefreshAction.launch(intent)
- }
-
- fun createFolder() {
- if (!validateState()) return
- FolderCreationDialogFragment.newInstance(currentDir.path).show(supportFragmentManager, null)
- }
-
- fun deletePasswords(selectedItems: List<PasswordItem>) {
- var size = 0
- selectedItems.forEach {
- if (it.file.isFile) size++ else size += it.file.listFilesRecursively().size
- }
- if (size == 0) {
- selectedItems.map { item -> item.file.deleteRecursively() }
- refreshPasswordList()
- return
- }
- MaterialAlertDialogBuilder(this)
- .setMessage(resources.getQuantityString(R.plurals.delete_dialog_text, size, size))
- .setPositiveButton(resources.getString(R.string.dialog_yes)) { _, _ ->
- val filesToDelete = arrayListOf<File>()
- selectedItems.forEach { item ->
- if (item.file.isDirectory) filesToDelete.addAll(item.file.listFilesRecursively())
- else filesToDelete.add(item.file)
- }
- selectedItems.map { item -> item.file.deleteRecursively() }
- refreshPasswordList()
- AutofillMatcher.updateMatches(applicationContext, delete = filesToDelete)
- val fmt =
- selectedItems.joinToString(separator = ", ") { item ->
- item.file.toRelativeString(PasswordRepository.getRepositoryDirectory())
- }
- lifecycleScope.launch {
- commitChange(resources.getString(R.string.git_commit_remove_text, fmt))
- }
- }
- .setNegativeButton(resources.getString(R.string.dialog_no), null)
- .show()
- }
-
- fun movePasswords(values: List<PasswordItem>) {
- val intent = Intent(this, SelectFolderActivity::class.java)
- val fileLocations = values.map { it.file.absolutePath }.toTypedArray()
- intent.putExtra("Files", fileLocations)
- passwordMoveAction.launch(intent)
- }
-
- enum class CategoryRenameError(val resource: Int) {
- None(0),
- EmptyField(R.string.message_category_error_empty_field),
- CategoryExists(R.string.message_category_error_category_exists),
- DestinationOutsideRepo(R.string.message_error_destination_outside_repo),
- }
-
- /**
- * Prompt the user with a new category name to assign, if the new category forms/leads a path
- * (i.e. contains "/"), intermediate directories will be created and new category will be placed
- * inside.
- *
- * @param oldCategory The category to change its name
- * @param error Determines whether to show an error to the user in the alert dialog, this error
- * may be due to the new category the user entered already exists or the field was empty or the
- * destination path is outside the repository
- * @see [CategoryRenameError]
- * @see [isInsideRepository]
- */
- private fun renameCategory(
- oldCategory: PasswordItem,
- error: CategoryRenameError = CategoryRenameError.None,
- ) {
- val view = layoutInflater.inflate(R.layout.folder_dialog_fragment, null)
- val newCategoryEditText = view.findViewById<TextInputEditText>(R.id.folder_name_text)
-
- if (error != CategoryRenameError.None) {
- newCategoryEditText.error = getString(error.resource)
- }
-
- val dialog =
- MaterialAlertDialogBuilder(this)
- .setTitle(R.string.title_rename_folder)
- .setView(view)
- .setMessage(getString(R.string.message_rename_folder, oldCategory.name))
- .setPositiveButton(R.string.dialog_ok) { _, _ ->
- val newCategory = File("${oldCategory.file.parent}/${newCategoryEditText.text}")
- when {
- newCategoryEditText.text.isNullOrBlank() ->
- renameCategory(oldCategory, CategoryRenameError.EmptyField)
- newCategory.exists() -> renameCategory(oldCategory, CategoryRenameError.CategoryExists)
- !newCategory.isInsideRepository() ->
- renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo)
- else ->
- lifecycleScope.launch(dispatcherProvider.io()) {
- moveFile(oldCategory.file, newCategory)
-
- // associate the new category with the last category's timestamp in
- // history
- val preference =
- getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
- val timestamp = preference.getString(oldCategory.file.absolutePath.base64())
- if (timestamp != null) {
- preference.edit {
- remove(oldCategory.file.absolutePath.base64())
- putString(newCategory.absolutePath.base64(), timestamp)
- }
- }
-
- withContext(dispatcherProvider.main()) {
- commitChange(
- resources.getString(
- R.string.git_commit_move_text,
- oldCategory.name,
- newCategory.name,
- )
- )
- }
- }
- }
- }
- .setNegativeButton(R.string.dialog_skip, null)
- .create()
-
- dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- dialog.show()
- }
-
- fun renameCategory(categories: List<PasswordItem>) {
- for (oldCategory in categories) {
- renameCategory(oldCategory)
- }
- }
-
- /**
- * Refreshes the password list by re-executing the last navigation or search action, preserving
- * the navigation stack and scroll position. If the current directory no longer exists, navigation
- * is reset to the repository root. If the optional [target] argument is provided, it will be
- * entered if it is a directory or scrolled into view if it is a file (both inside the current
- * directory).
- */
- fun refreshPasswordList(target: File? = null) {
- val plist = getPasswordFragment()
- if (target?.isDirectory == true && model.currentDir.value.contains(target)) {
- plist?.navigateTo(target)
- } else if (target?.isFile == true && model.currentDir.value.contains(target)) {
- // Creating new passwords is handled by an activity, so we will refresh in onStart.
- plist?.scrollToOnNextRefresh(target)
- } else if (model.currentDir.value.isDirectory) {
- model.forceRefresh()
- } else {
- model.reset()
- supportActionBar?.setDisplayHomeAsUpEnabled(false)
- }
- }
-
- private val currentDir: File
- get() = getPasswordFragment()?.currentDir ?: PasswordRepository.getRepositoryDirectory()
-
- private suspend fun moveFile(source: File, destinationFile: File) {
- val sourceDestinationMap =
- if (source.isDirectory) {
- destinationFile.mkdirs()
- // Recursively list all files (not directories) below `source`, then
- // obtain the corresponding target file by resolving the relative path
- // starting at the destination folder.
- source.listFilesRecursively().associateWith {
- destinationFile.resolve(it.relativeTo(source))
- }
- } else {
- mapOf(source to destinationFile)
- }
- if (!source.renameTo(destinationFile)) {
- logcat(ERROR) { "Something went wrong while moving $source to $destinationFile." }
- withContext(dispatcherProvider.main()) {
- MaterialAlertDialogBuilder(this@PasswordStore)
- .setTitle(R.string.password_move_error_title)
- .setMessage(getString(R.string.password_move_error_message, source, destinationFile))
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok, null)
- .show()
- }
- } else {
- AutofillMatcher.updateMatches(this, sourceDestinationMap)
- }
- }
-
- fun matchPasswordWithApp(item: PasswordItem) {
- val path =
- item.file.absolutePath
- .replace(PasswordRepository.getRepositoryDirectory().toString() + "/", "")
- .replace(".gpg", "")
- val data = Intent()
- data.putExtra("path", path)
- setResult(RESULT_OK, data)
- finish()
- }
-
- companion object {
-
- const val REQUEST_ARG_PATH = "PATH"
-
- private fun isPrintable(c: Char): Boolean {
- val block = UnicodeBlock.of(c)
- return (!Character.isISOControl(c) && block != null && block !== UnicodeBlock.SPECIALS)
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/util/OnOffItemAnimator.kt b/app/src/main/java/app/passwordstore/ui/util/OnOffItemAnimator.kt
deleted file mode 100644
index cd29f626..00000000
--- a/app/src/main/java/app/passwordstore/ui/util/OnOffItemAnimator.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.ui.util
-
-import androidx.recyclerview.widget.DefaultItemAnimator
-import androidx.recyclerview.widget.RecyclerView
-
-class OnOffItemAnimator : DefaultItemAnimator() {
-
- var isEnabled: Boolean = true
- set(value) {
- // Defer update until no animation is running anymore.
- isRunning { field = value }
- }
-
- private fun dontAnimate(viewHolder: RecyclerView.ViewHolder): Boolean {
- dispatchAnimationFinished(viewHolder)
- return false
- }
-
- override fun animateAppearance(
- viewHolder: RecyclerView.ViewHolder,
- preLayoutInfo: ItemHolderInfo?,
- postLayoutInfo: ItemHolderInfo,
- ): Boolean {
- return if (isEnabled) {
- super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo)
- } else {
- dontAnimate(viewHolder)
- }
- }
-
- override fun animateChange(
- oldHolder: RecyclerView.ViewHolder,
- newHolder: RecyclerView.ViewHolder,
- preInfo: ItemHolderInfo,
- postInfo: ItemHolderInfo,
- ): Boolean {
- return if (isEnabled) {
- super.animateChange(oldHolder, newHolder, preInfo, postInfo)
- } else {
- dontAnimate(oldHolder)
- }
- }
-
- override fun animateDisappearance(
- viewHolder: RecyclerView.ViewHolder,
- preLayoutInfo: ItemHolderInfo,
- postLayoutInfo: ItemHolderInfo?,
- ): Boolean {
- return if (isEnabled) {
- super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo)
- } else {
- dontAnimate(viewHolder)
- }
- }
-
- override fun animatePersistence(
- viewHolder: RecyclerView.ViewHolder,
- preInfo: ItemHolderInfo,
- postInfo: ItemHolderInfo,
- ): Boolean {
- return if (isEnabled) {
- super.animatePersistence(viewHolder, preInfo, postInfo)
- } else {
- dontAnimate(viewHolder)
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/util/autofill/AutofillViewUtils.kt b/app/src/main/java/app/passwordstore/util/autofill/AutofillViewUtils.kt
index ebe4bc7d..59fd5cca 100644
--- a/app/src/main/java/app/passwordstore/util/autofill/AutofillViewUtils.kt
+++ b/app/src/main/java/app/passwordstore/util/autofill/AutofillViewUtils.kt
@@ -20,7 +20,7 @@ import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi
import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.passwords.PasswordStore
+import app.passwordstore.ui.main.LaunchActivity
import java.io.File
data class DatasetMetadata(val title: String, val subtitle: String?, @DrawableRes val iconRes: Int)
@@ -55,7 +55,7 @@ fun makeInlinePresentation(
PendingIntent.getActivity(
context,
0,
- Intent(context, PasswordStore::class.java),
+ Intent(context, LaunchActivity::class.java),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE
} else {
diff --git a/app/src/main/res/anim/scale_down.xml b/app/src/main/res/anim/scale_down.xml
deleted file mode 100644
index cbb4cb97..00000000
--- a/app/src/main/res/anim/scale_down.xml
+++ /dev/null
@@ -1,16 +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
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
- <scale
- android:duration="300"
- android:fromXScale="1.0"
- android:fromYScale="1.0"
- android:interpolator="@android:anim/linear_interpolator"
- android:pivotX="50%"
- android:pivotY="50%"
- android:toXScale="0"
- android:toYScale="0" />
-</set>
diff --git a/app/src/main/res/anim/scale_up.xml b/app/src/main/res/anim/scale_up.xml
deleted file mode 100644
index 1e149e38..00000000
--- a/app/src/main/res/anim/scale_up.xml
+++ /dev/null
@@ -1,16 +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
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
- <scale
- android:duration="300"
- android:fromXScale="0"
- android:fromYScale="0"
- android:interpolator="@android:anim/linear_interpolator"
- android:pivotX="50%"
- android:pivotY="50%"
- android:toXScale="1.0"
- android:toYScale="1.0" />
-</set>
diff --git a/app/src/main/res/layout/activity_pwdstore.xml b/app/src/main/res/layout/activity_pwdstore.xml
deleted file mode 100644
index 295efd9c..00000000
--- a/app/src/main/res/layout/activity_pwdstore.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- tools:context=".PasswordStore">
-
- <LinearLayout
- android:id="@+id/main_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" />
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/folder_dialog_fragment.xml b/app/src/main/res/layout/folder_dialog_fragment.xml
deleted file mode 100644
index 7860c9b4..00000000
--- a/app/src/main/res/layout/folder_dialog_fragment.xml
+++ /dev/null
@@ -1,38 +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:orientation="vertical"
- android:padding="16dp">
-
- <com.google.android.material.textfield.TextInputLayout
- android:id="@+id/folder_name_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/crypto_name_hint"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent">
-
- <com.google.android.material.textfield.TextInputEditText
- android:id="@+id/folder_name_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:inputType="textNoSuggestions|textVisiblePassword" />
- <requestFocus />
- </com.google.android.material.textfield.TextInputLayout>
-
- <!-- TODO(msfjarvis): Restore this functionality -->
- <com.google.android.material.checkbox.MaterialCheckBox
- android:id="@+id/set_gpg_key"
- android:layout_width="0dp"
- android:visibility="gone"
- android:layout_height="wrap_content"
- android:text="@string/new_folder_set_gpg_key"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/folder_name_container" />
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/item_create_sheet.xml b/app/src/main/res/layout/item_create_sheet.xml
deleted file mode 100644
index 10882a00..00000000
--- a/app/src/main/res/layout/item_create_sheet.xml
+++ /dev/null
@@ -1,42 +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">
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/create_folder"
- style="@style/Widget.MaterialComponents.Button.TextButton"
- android:layout_width="match_parent"
- android:layout_height="@dimen/bottom_sheet_item_height"
- android:layout_margin="@dimen/normal_margin"
- android:text="@string/bottom_sheet_create_new_folder"
- android:textAlignment="viewStart"
- android:textColor="?attr/colorOnSurface"
- app:icon="@drawable/ic_action_new_folder"
- app:iconPadding="@dimen/normal_margin"
- app:iconTint="?attr/colorSecondary"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:rippleColor="?attr/colorSecondary" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/create_password"
- style="@style/Widget.MaterialComponents.Button.TextButton"
- android:layout_width="match_parent"
- android:layout_height="@dimen/bottom_sheet_item_height"
- android:layout_margin="@dimen/normal_margin"
- android:text="@string/bottom_sheet_create_new_password"
- android:textAlignment="viewStart"
- android:textColor="?attr/colorOnSurface"
- app:icon="@drawable/ic_action_new_password"
- app:iconPadding="@dimen/normal_margin"
- app:iconTint="?attr/colorSecondary"
- app:layout_constraintTop_toBottomOf="@id/create_folder"
- app:rippleColor="?attr/colorSecondary" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/password_recycler_view.xml b/app/src/main/res/layout/password_recycler_view.xml
index 49b7ff82..c2b5114f 100644
--- a/app/src/main/res/layout/password_recycler_view.xml
+++ b/app/src/main/res/layout/password_recycler_view.xml
@@ -9,8 +9,7 @@
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
- android:orientation="vertical"
- tools:context="app.passwordstore.ui.passwords.PasswordFragment">
+ android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresher"
diff --git a/app/src/main/res/menu/context_pass.xml b/app/src/main/res/menu/context_pass.xml
deleted file mode 100644
index 608d7eef..00000000
--- a/app/src/main/res/menu/context_pass.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- tools:context=".pwdstore">
-
- <item
- android:id="@+id/menu_move_password"
- android:icon="@drawable/ic_move_folder_24dp"
- android:title="@string/move"
- app:showAsAction="ifRoom" />
-
- <item
- android:id="@+id/menu_delete_password"
- android:icon="@drawable/ic_delete_24dp"
- android:title="@string/delete"
- app:showAsAction="ifRoom" />
-
- <item
- android:id="@+id/menu_edit_password"
- android:icon="@drawable/ic_edit_24dp"
- android:title="@string/edit"
- app:showAsAction="ifRoom" />
-
- <item
- android:id="@+id/menu_pin_password"
- android:icon="@drawable/ic_push_pin_24dp"
- android:title="@string/place_shortcut_on_home_screen"
- app:showAsAction="ifRoom" />
-</menu>
diff --git a/app/src/main/res/menu/main_menu_git.xml b/app/src/main/res/menu/main_menu_git.xml
deleted file mode 100644
index 1b64c083..00000000
--- a/app/src/main/res/menu/main_menu_git.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <item
- android:id="@+id/action_search"
- android:icon="@drawable/ic_search_24dp"
- android:title="@string/action_search"
- app:actionViewClass="androidx.appcompat.widget.SearchView"
- app:showAsAction="always|collapseActionView" />
-
- <item
- android:id="@+id/git_sync"
- android:title="@string/git_sync" />
-
- <item
- android:id="@+id/git_pull"
- android:title="@string/git_pull" />
-
- <item
- android:id="@+id/git_push"
- android:title="@string/git_push" />
-
- <item
- android:id="@+id/refresh"
- android:title="@string/refresh_list" />
-
- <item
- android:id="@+id/user_pref"
- android:orderInCategory="100"
- android:title="@string/action_settings" />
-</menu>
diff --git a/app/src/main/res/menu/main_menu_no_auth.xml b/app/src/main/res/menu/main_menu_no_auth.xml
deleted file mode 100644
index 46f19e49..00000000
--- a/app/src/main/res/menu/main_menu_no_auth.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <item
- android:id="@+id/action_search"
- android:icon="@drawable/ic_search_24dp"
- android:title="@string/action_search"
- app:actionViewClass="androidx.appcompat.widget.SearchView"
- app:showAsAction="always|collapseActionView" />
-
- <item
- android:id="@+id/git_pull"
- android:title="@string/git_pull" />
-
- <item
- android:id="@+id/refresh"
- android:title="@string/refresh_list" />
-
- <item
- android:id="@+id/user_pref"
- android:orderInCategory="100"
- android:title="@string/action_settings" />
-</menu>
diff --git a/app/src/main/res/menu/main_menu_non_git.xml b/app/src/main/res/menu/main_menu_non_git.xml
deleted file mode 100644
index 5a7cf00b..00000000
--- a/app/src/main/res/menu/main_menu_non_git.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <item
- android:id="@+id/action_search"
- android:icon="@drawable/ic_search_24dp"
- android:title="@string/action_search"
- app:actionViewClass="androidx.appcompat.widget.SearchView"
- app:showAsAction="always|collapseActionView" />
-
- <item
- android:id="@+id/refresh"
- android:title="@string/refresh_list" />
-
- <item
- android:id="@+id/user_pref"
- android:orderInCategory="100"
- android:title="@string/action_settings" />
-</menu>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7499fe5e..d13ab19f 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -24,7 +24,6 @@
</plurals>
<string name="delete_directory_progress_text">Wird gelöscht</string>
<string name="move">Verschieben</string>
- <string name="edit">Bearbeiten</string>
<string name="delete">Löschen</string>
<string name="password_exists_title">Passwort existiert bereits!</string>
<string name="password_exists_message">Dies wird %1$s mit %2$s überschreiben.</string>
@@ -167,7 +166,6 @@
<string name="dialog_yes">Ja</string>
<string name="dialog_no">Nein</string>
<string name="dialog_cancel">Abbruch</string>
- <string name="dialog_skip">Überspringen</string>
<string name="git_sync">Synchronisiere Repository</string>
<string name="git_pull">Git Pull</string>
<string name="git_push">Git Push</string>
@@ -176,7 +174,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="pwd_generate_button">Erstellen</string>
- <string name="refresh_list">Liste aktualisieren</string>
<string name="send_plaintext_password_to">Passwort unverschlüsselt senden an…</string>
<string name="app_icon_hint">App-Icon</string>
<!-- Oreo Autofill -->
@@ -234,7 +231,6 @@
<string name="title_create_folder">Ordner erstellen</string>
<string name="title_rename_folder">Ordner umbenennen</string>
<string name="message_category_error_empty_field">Der Name der Kategorie darf nicht leer sein.</string>
- <string name="message_category_error_category_exists">Der Kategoriename existiert bereits.</string>
<string name="message_error_destination_outside_repo">Ziel muss innerhalb des Repositorys sein.</string>
<string name="message_rename_folder">Ziel für %1$s angeben</string>
<string name="button_create">Erstellen</string>
@@ -281,7 +277,6 @@
<string name="short_gpg_id">.gpg-id wurde gefunden, enthält jedoch eine kurze Hex-ID, die nicht unterstützt wird.</string>
<string name="invalid_filename_text">Dateiname darf kein \'/\' enthalten; Verzeichnis oben setzen</string>
<string name="directory_hint">Ordner</string>
- <string name="new_folder_set_gpg_key">PGP-Schlüssel für Ordner festlegen</string>
<!-- GitException messages -->
<string name="git_unknown_error">Unbekannter Fehler</string>
<string name="git_pull_rebase_fail_error">Pull fehlgeschlagen. Sie befinden sich in einem abgelösten Head. Speichern Sie die Änderungen in \"Einstellungen &gt; Lokale Git-Konfiguration &amp; Dienstprogramme\" auf das Remote-Repository in einem neuen Branch und lösen Sie den Konflikt auf Ihrem Computer auf.</string>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 6c8ac709..0cfb345c 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -24,7 +24,6 @@
</plurals>
<string name="delete_directory_progress_text">Suppression…</string>
<string name="move">Déplacer</string>
- <string name="edit">Éditer</string>
<string name="delete">Supprimer</string>
<string name="password_exists_title">Le mot de passe existe!</string>
<string name="password_exists_message">Cela écrasera %1$s avec %2$s.</string>
@@ -155,7 +154,6 @@
<string name="dialog_yes">Oui</string>
<string name="dialog_no">Non</string>
<string name="dialog_cancel">Annuler</string>
- <string name="dialog_skip">Ignorer</string>
<string name="git_sync">Synchronisation du dépôt</string>
<string name="git_pull">Importer du serveur</string>
<string name="git_push">Pousser sur le serveur</string>
@@ -164,7 +162,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="pwd_generate_button">Générer</string>
- <string name="refresh_list">Rafraichir la liste</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 -->
@@ -222,7 +219,6 @@
<string name="title_create_folder">Créer un dossier</string>
<string name="title_rename_folder">Renommer le dossier</string>
<string name="message_category_error_empty_field">Le nom de la catégorie ne peut pas être vide</string>
- <string name="message_category_error_category_exists">Une catégorie du même nom existe déjà</string>
<string name="message_error_destination_outside_repo">La destination doit être à l\'intérieur du dépôt</string>
<string name="message_rename_folder">Entrez le chemin pour %1$s</string>
<string name="button_create">Créer</string>
@@ -266,7 +262,6 @@
<string name="exporting_passwords">Exportation des mots de passe…</string>
<string name="invalid_filename_text">Le nom du fichier ne doit pas contenir \'/\', définir le répertoire parent</string>
<string name="directory_hint">Dossier</string>
- <string name="new_folder_set_gpg_key">Définir la clé GPG pour le dossier</string>
<!-- GitException messages -->
<string name="git_unknown_error">Erreur inconnue</string>
<string name="git_pull_rebase_fail_error">Tirer a échoué, vous êtes dans un \'head\' détaché. En utilisant \"settings &gt; git utils\", enregistrez vos modifications sur le dépôt distant dans une nouvelle branche et résolvez le conflit sur votre ordinateur.</string>
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 4ca39712..589208ab 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -24,7 +24,6 @@
</plurals>
<string name="delete_directory_progress_text">Eliminando…</string>
<string name="move">Mover</string>
- <string name="edit">Editar</string>
<string name="delete">Eliminar</string>
<string name="password_exists_title">O contrasinal xa existe!</string>
<string name="password_exists_message">Vas sobrescribir %1$s con %2$s.</string>
@@ -168,7 +167,6 @@
<string name="dialog_yes">Si</string>
<string name="dialog_no">Non</string>
<string name="dialog_cancel">Cancelar</string>
- <string name="dialog_skip">Omitir</string>
<string name="git_sync">Sincronizar repositorio</string>
<string name="git_pull">Traer desde remoto</string>
<string name="git_push">Enviar a remoto</string>
@@ -177,7 +175,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="pwd_generate_button">Crear</string>
- <string name="refresh_list">Actualizar lista</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 -->
@@ -236,7 +233,6 @@ a app desde unha fonte de confianza, como a Play Store, Amazon Appstore, F-Droid
<string name="title_create_folder">Crear cartafol</string>
<string name="title_rename_folder">Renomear cartafol</string>
<string name="message_category_error_empty_field">O nome da categoría non pode estar baleiro</string>
- <string name="message_category_error_category_exists">Xa existe o nome da categoría</string>
<string name="message_error_destination_outside_repo">O destino debe estar dentro do repositorio</string>
<string name="message_rename_folder">Escribe o destino para %1$s</string>
<string name="button_create">Crear</string>
@@ -283,7 +279,6 @@ a app desde unha fonte de confianza, como a Play Store, Amazon Appstore, F-Droid
<string name="short_gpg_id">Atopouse .gpg-id, pero contén un ID hex curto, ao que non damos soporte</string>
<string name="invalid_filename_text">O nome do ficheiro non pode conter \'/\', establece un directorio superior</string>
<string name="directory_hint">Directorio</string>
- <string name="new_folder_set_gpg_key">Establece chave GPG para o directorio</string>
<!-- GitException messages -->
<string name="git_unknown_error">Fallo descoñecido</string>
<string name="git_pull_rebase_fail_error">Fallou a acción pull, estás nun head diferente. Utiliza \"axustes &gt; utilidades git\", garda os cambios no remoto nunha nova rama e resolve o conflicto nun ordenador.</string>
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 95893b17..5ed24efb 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -24,7 +24,6 @@
</plurals>
<string name="delete_directory_progress_text">Eliminazione…</string>
<string name="move">Sposta</string>
- <string name="edit">Modifica</string>
<string name="delete">Elimina</string>
<string name="password_exists_title">La password esiste già!</string>
<string name="password_exists_message">Questo sovrascriverà %1$s con %2$s.</string>
@@ -150,7 +149,6 @@
<string name="dialog_yes">Sì</string>
<string name="dialog_no">No</string>
<string name="dialog_cancel">Annulla</string>
- <string name="dialog_skip">Salta</string>
<string name="git_sync">Sincronizza repository</string>
<string name="git_pull">Pull dal remote</string>
<string name="git_push">Push sul remote</string>
@@ -159,7 +157,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="pwd_generate_button">Genera</string>
- <string name="refresh_list">Aggiorna elenco</string>
<string name="send_plaintext_password_to">Invia password come testo semplice usando…</string>
<string name="app_icon_hint">Icona app</string>
<!-- Oreo Autofill -->
@@ -217,7 +214,6 @@
<string name="title_create_folder">Crea cartella</string>
<string name="title_rename_folder">Rinomina cartella</string>
<string name="message_category_error_empty_field">Il nome della categoria non può essere vuoto</string>
- <string name="message_category_error_category_exists">Il nome della categoria esiste già</string>
<string name="message_error_destination_outside_repo">La destinazione deve essere nel repository</string>
<string name="message_rename_folder">Inserisci la destinazione per %1$s</string>
<string name="button_create">Crea</string>
@@ -261,7 +257,6 @@
<string name="exporting_passwords">Esportazione delle password…</string>
<string name="invalid_filename_text">Il nome del file non deve contenere \'/\', imposta la directory qui sopra</string>
<string name="directory_hint">Directory</string>
- <string name="new_folder_set_gpg_key">Imposta la chiave GPG per la directory</string>
<!-- GitException messages -->
<string name="git_unknown_error">Errore sconosciuto</string>
<string name="git_pull_rebase_fail_error">Pull non riuscito, sei in detached HEAD. Da \"Impostazioni > Utilità Git\" esegui il push delle modifiche sul remote in un nuovo branch e risolvi il conflitto dal computer.</string>
diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml
index 432b6fbe..7f63ad8b 100644
--- a/app/src/main/res/values-ko-rKR/strings.xml
+++ b/app/src/main/res/values-ko-rKR/strings.xml
@@ -22,7 +22,6 @@
</plurals>
<string name="delete_directory_progress_text">삭제중…</string>
<string name="move">이동</string>
- <string name="edit">수정</string>
<string name="delete">삭제</string>
<string name="password_exists_title">이미 추가된 비밀번호입니다!</string>
<string name="password_exists_message">%1$s 내용이 %2$s 로 덮어씌워집니다.</string>
@@ -163,7 +162,6 @@
<string name="dialog_yes">예</string>
<string name="dialog_no">아니오</string>
<string name="dialog_cancel">취소</string>
- <string name="dialog_skip">건너뛰기</string>
<string name="git_sync">저장소 동기화</string>
<string name="git_pull">원격에서 pull</string>
<string name="git_push">원격으로 푸시</string>
@@ -172,7 +170,6 @@
<string name="show_password_pref_title">비밀번호 표시하기</string>
<string name="show_password_pref_summary">암호가 해독된 후 비밀번호의 공개 여부를 제어합니다. 그렇다고 해서 클립보드 복사가 비활성화되지는 않습니다.</string>
<string name="pwd_generate_button">생성</string>
- <string name="refresh_list">목록 새로 고침</string>
<string name="send_plaintext_password_to">비밀번호를 일반 텍스트로 보내는 중…</string>
<string name="app_icon_hint">앱 아이콘</string>
<!-- Oreo Autofill -->
@@ -230,7 +227,6 @@
<string name="title_create_folder">폴더 생성</string>
<string name="title_rename_folder">폴더 이름 변경</string>
<string name="message_category_error_empty_field">카테고리 이름은 비워 둘 수 없습니다</string>
- <string name="message_category_error_category_exists">카테고리가 이미 있습니다</string>
<string name="message_error_destination_outside_repo">대상은 리포지토리 내에 있어야 합니다</string>
<string name="message_rename_folder">%1$s의 목적지 입력</string>
<string name="button_create">생성</string>
@@ -279,7 +275,6 @@ username@example.com:…</string>
<string name="short_gpg_id">.gpg-id를 찾았지만, 지원되지 않는 짧은 16진수 ID가 포함되어 있습니다.</string>
<string name="invalid_filename_text">파일 이름에 \'/\'가 포함되어서는 안됩니다. 위의 디렉터리를 설정하세요</string>
<string name="directory_hint">디렉토리</string>
- <string name="new_folder_set_gpg_key">디렉토리의 GPG 키 설정</string>
<!-- GitException messages -->
<string name="git_unknown_error">알 수 없는 오류</string>
<string name="git_pull_rebase_fail_error">풀이 실패하여 head가 분리된 상태입니다. \"설정 &gt; git 기능\"을 사용하여 원격 저장소에 변경 내용을 새 브랜치에 저장하고 컴퓨터에서 충돌을 해결하세요.</string>
diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml
index a915f67d..a2068e65 100644
--- a/app/src/main/res/values-pl-rPL/strings.xml
+++ b/app/src/main/res/values-pl-rPL/strings.xml
@@ -23,7 +23,6 @@
<string name="creation_dialog_text">Zanim dodasz hasło lub rozpoczniesz synchronizację, stwórz albo sklonuj poniżej nowe repozytorium.</string>
<string name="delete_directory_progress_text">Usuwanie…</string>
<string name="move">Przenieś</string>
- <string name="edit">Edytuj</string>
<string name="delete">Usuń</string>
<string name="password_exists_title">Hasło już istnieje!</string>
<string name="password_exists_message">%1$s zostanie nadpisane przez %2$s.</string>
@@ -164,7 +163,6 @@
<string name="dialog_yes">Tak</string>
<string name="dialog_no">Nie</string>
<string name="dialog_cancel">Anuluj</string>
- <string name="dialog_skip">Pomiń</string>
<string name="git_sync">Synchronizuj repozytorium</string>
<string name="git_pull">Pobierz ze zdalnego repozytorium</string>
<string name="git_push">Wypchnij do zdalnego repozytorium</string>
@@ -173,7 +171,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="pwd_generate_button">Generuj</string>
- <string name="refresh_list">Odśwież listę</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 -->
@@ -229,7 +226,6 @@
<string name="title_create_folder">Utwórz katalog</string>
<string name="title_rename_folder">Zmień nazwę katalogu</string>
<string name="message_category_error_empty_field">Nazwa kategorii nie może być pusta</string>
- <string name="message_category_error_category_exists">Kategoria już istnieje</string>
<string name="message_error_destination_outside_repo">Miejsce docelowe musi znajdować się w repozytorium</string>
<string name="message_rename_folder">Wprowadź miejsce docelowe dla %1$s</string>
<string name="button_create">Utwórz</string>
@@ -273,7 +269,6 @@
<string name="short_gpg_id">Znaleziono .gpg-id, ale zawarty w nim skrócony identyfikator nie jest obsługiwany</string>
<string name="invalid_filename_text">Nazwa pliku nie może zawierać \'/\', ustaw katalog powyżej</string>
<string name="directory_hint">Katalog</string>
- <string name="new_folder_set_gpg_key">Ustaw klucz GPG dla katalogu</string>
<!-- GitException messages -->
<string name="git_unknown_error">Nieznany błąd</string>
<string name="git_pull_rebase_fail_error">Zaciąganie zmian nie powiodło się, nie jesteś na żadnej gałęzi (detached head). Używając \"Ustawienia &gt; narzędzia Git\", wyślij lokalne zmiany do zdalnego repozytorium na nowej gałęzi i rozwiąż konflikt na komputerze.</string>
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 1d95286d..909fd33e 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -24,7 +24,6 @@
</plurals>
<string name="delete_directory_progress_text">Excluindo…</string>
<string name="move">Mover</string>
- <string name="edit">Editar</string>
<string name="delete">Excluir</string>
<string name="password_exists_title">Senha já existe!</string>
<string name="password_exists_message">Isso irá sobrescrever %1$s com %2$s.</string>
@@ -167,7 +166,6 @@
<string name="dialog_yes">Sim</string>
<string name="dialog_no">Não</string>
<string name="dialog_cancel">Cancelar</string>
- <string name="dialog_skip">Pular</string>
<string name="git_sync">Sincronizar repositório</string>
<string name="git_pull">Pull do remoto</string>
<string name="git_push">Push do remoto</string>
@@ -176,7 +174,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="pwd_generate_button">Gerar</string>
- <string name="refresh_list">Atualizar lista</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 -->
@@ -234,7 +231,6 @@
<string name="title_create_folder">Criar pasta</string>
<string name="title_rename_folder">Renomear pasta</string>
<string name="message_category_error_empty_field">O nome da categoria não pode ser vazio</string>
- <string name="message_category_error_category_exists">Nome da categoria já existe</string>
<string name="message_error_destination_outside_repo">O destino deve estar dentro do repositório</string>
<string name="message_rename_folder">Insira o destino para %1$s</string>
<string name="button_create">Criar</string>
@@ -281,7 +277,6 @@
<string name="short_gpg_id">Encontrado .gpg-id, mas contém uma ID hexadecimal curta, que não é suportada</string>
<string name="invalid_filename_text">Nome do arquivo não deve conter \'/\', defina o diretório acima</string>
<string name="directory_hint">Diretório</string>
- <string name="new_folder_set_gpg_key">Definir chave GPG para diretório</string>
<!-- GitException messages -->
<string name="git_unknown_error">Erro desconhecido</string>
<string name="git_pull_rebase_fail_error">O pull falhou, você está em uma Head avulsa. Usando \"configurações &gt; utils\" do git, salve suas alterações no remoto em uma nova branch e resolva o conflito no seu computador.</string>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index c10bdcd8..2ef59d70 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -28,7 +28,6 @@
</plurals>
<string name="delete_directory_progress_text">Удаляется…</string>
<string name="move">Переместить</string>
- <string name="edit">Редактировать</string>
<string name="delete">Удалить</string>
<string name="password_exists_title">Пароль уже существует!</string>
<string name="password_exists_message">Это перезапишет %1$s на %2$s</string>
@@ -160,7 +159,6 @@
<string name="dialog_yes">Да</string>
<string name="dialog_no">Нет</string>
<string name="dialog_cancel">Отмена</string>
- <string name="dialog_skip">Пропустить</string>
<string name="git_sync">Синхронизировать репозиторий</string>
<string name="git_pull">Пулл с удаленного сервера</string>
<string name="git_push">Пуш на удаленный сервер</string>
@@ -169,7 +167,6 @@
<string name="show_password_pref_title">Показывать пароли</string>
<string name="show_password_pref_summary">Видимость расшифрованных паролей, не влияет на копирование в буфер.</string>
<string name="pwd_generate_button">Сгенерировать</string>
- <string name="refresh_list">Обновить список</string>
<string name="send_plaintext_password_to">Поделиться паролем в открытом виде с помощью</string>
<string name="app_icon_hint">Иконка приложения</string>
<!-- Oreo Autofill -->
@@ -227,7 +224,6 @@
<string name="title_create_folder">Создать папку</string>
<string name="title_rename_folder">Переименовать папку</string>
<string name="message_category_error_empty_field">Название категории не может быть пустым</string>
- <string name="message_category_error_category_exists">Название категории уже существует</string>
<string name="message_error_destination_outside_repo">Путь должен указывать на область внутри репозитория</string>
<string name="message_rename_folder">Введите адрес назначения для %1$s</string>
<string name="button_create">Создать</string>
@@ -273,7 +269,6 @@
<string name="invalid_gpg_id">Найден .gpg-id, но он содержит неверный ID ключа, fingerprint или ID пользователя</string>
<string name="invalid_filename_text">Имя файла не должно содержать \'/\', укажите директорию выше</string>
<string name="directory_hint">Директория</string>
- <string name="new_folder_set_gpg_key">Установить GPG ключ для каталога</string>
<!-- GitException messages -->
<string name="git_unknown_error">Неизвестная ошибка</string>
<string name="git_pull_rebase_fail_error">Не удалось получить изменения, вы находитесь в состоянии \"оторванной головы\". Используйте \"настройки &gt; утилиты git\", сохраните ваши изменения в новую удаленную ветку и разрешите конфликты на своем компьютере.</string>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index b20749b3..55fbeefa 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -23,7 +23,6 @@
</plurals>
<string name="delete_directory_progress_text">正在删除…</string>
<string name="move">更多</string>
- <string name="edit">编辑</string>
<string name="delete">删除</string>
<string name="password_exists_title">密码已经存在!</string>
<string name="password_exists_message">这将用%2$s覆盖%1$s</string>
@@ -168,7 +167,6 @@
<string name="dialog_yes">同意</string>
<string name="dialog_no">否定</string>
<string name="dialog_cancel">取消</string>
- <string name="dialog_skip">跳过</string>
<string name="git_sync">同步存储库</string>
<string name="git_pull">拉取远程数据</string>
<string name="git_push">推送数据到远程</string>
@@ -177,7 +175,6 @@
<string name="show_password_pref_title">显示密码</string>
<string name="show_password_pref_summary">控制密码解密后的可见性,这不会禁用复制到剪贴板</string>
<string name="pwd_generate_button">生成</string>
- <string name="refresh_list">刷新列表</string>
<string name="send_plaintext_password_to">使用明文发送密码…</string>
<string name="app_icon_hint">应用图标</string>
<!-- Oreo Autofill -->
@@ -235,7 +232,6 @@
<string name="title_create_folder">清除目录</string>
<string name="title_rename_folder">重命名目录</string>
<string name="message_category_error_empty_field">类别名称不能为空</string>
- <string name="message_category_error_category_exists">类别名称已存在</string>
<string name="message_error_destination_outside_repo">目标必须在存储库中</string>
<string name="message_rename_folder">输入%1$s的目标</string>
<string name="button_create">创建</string>
@@ -283,7 +279,6 @@ personal.com</string>
<string name="short_gpg_id">找到 .gpg-id 但它包含一个不受支持的短十六进制ID。</string>
<string name="invalid_filename_text">文件名不能包含\'/\'</string>
<string name="directory_hint">目录</string>
- <string name="new_folder_set_gpg_key">为目录设置GPG密钥</string>
<!-- GitException messages -->
<string name="git_unknown_error">未知错误</string>
<string name="git_pull_merge_fail_error">合并失败,你处于冲突状态。</string>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index d6939e63..e5d7ff94 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -9,7 +9,6 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_compat_margin">16dp</dimen>
<dimen name="normal_margin">8dp</dimen>
- <dimen name="bottom_sheet_item_height">56dp</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>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b280e9f2..9e402f6b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -30,7 +30,6 @@
</plurals>
<string name="delete_directory_progress_text">Deleting…</string>
<string name="move">Move</string>
- <string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="password_exists_title">Password already exists!</string>
<string name="password_exists_message">This will overwrite %1$s with %2$s.</string>
@@ -195,7 +194,6 @@
<string name="dialog_yes">Yes</string>
<string name="dialog_no">No</string>
<string name="dialog_cancel">Cancel</string>
- <string name="dialog_skip">Skip</string>
<string name="git_sync">Synchronize repository</string>
<string name="git_pull">Pull from remote</string>
<string name="git_push">Push to remote</string>
@@ -204,7 +202,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="pwd_generate_button">Generate</string>
- <string name="refresh_list">Refresh list</string>
<string name="send_plaintext_password_to">Send password as plaintext using…</string>
<string name="app_icon_hint">App icon</string>
@@ -267,7 +264,6 @@
<string name="title_create_folder">Create folder</string>
<string name="title_rename_folder">Rename folder</string>
<string name="message_category_error_empty_field">Category name can\'t be empty</string>
- <string name="message_category_error_category_exists">Category name already exists</string>
<string name="message_error_destination_outside_repo">Destination must be within the repository</string>
<string name="message_rename_folder">Enter destination for %1$s</string>
<string name="button_create">Create</string>
@@ -316,7 +312,6 @@
<string name="short_gpg_id">Found .gpg-id, but it contains a short hex ID, which is not supported</string>
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
<string name="directory_hint">Directory</string>
- <string name="new_folder_set_gpg_key">Set GPG key for directory</string>
<!-- GitException messages -->
<string name="git_unknown_error">Unknown error</string>