summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiogenes Molinares <amolinares19@gmail.com>2020-06-18 14:07:26 +0200
committerGitHub <noreply@github.com>2020-06-18 14:07:26 +0200
commit23b488a8eb69c99d5337538cc17690d2b637b9be (patch)
treedea8de23cb6f9e4e9134e75a8ee917080b3d38aa
parent33b3f54921bba7549933c6f889fbe7ab63b61b7c (diff)
Add support for category renaming (#854)
* rename category * changed CHANGELOG * IDE Refactor * Address review comments Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * change Stack to List and fix bug when empty category name * create intermediate folders * little fixes and KDoc added * Reuse existing move code * change button Cancel => Skip * use canonicalPath to confirm destination inside repository * change error message * update KDoc * show different error to user Co-authored-by: Harsh Shandilya <me@msfjarvis.dev> Co-authored-by: Harsh Shandilya <msfjarvis@gmail.com> Co-authored-by: Fabian Henneke <fabian@henneke.me> Co-authored-by: Fabian Henneke <FabianHenneke@users.noreply.github.com>
-rw-r--r--CHANGELOG.md2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt8
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt74
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt2
-rw-r--r--app/src/main/res/layout/folder_dialog_fragment.xml (renamed from app/src/main/res/layout/folder_creation_dialog_fragment.xml)0
-rw-r--r--app/src/main/res/menu/context_pass.xml5
-rw-r--r--app/src/main/res/values/strings.xml6
7 files changed, 91 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f84343f..1dc30484 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ All notable changes to this project will be documented in this file.
- Add support for better, more secure Keyex's and MACs with a brand new SSH backend
- Allow manually marking domains for subdomain-level association. This will allow you to keep separate passwords for `site1.example.com` and `site2.example.com` and have them show as such in Autofill.
- Provide better messages for OpenKeychain errors
-- Rename passwords
+- Rename passwords and categories
### Changed
- **BREAKING**: Remove support for HOTP/TOTP secrets - Please use FIDO keys or a dedicated app like [Aegis](https://github.com/beemdevelopment/Aegis) or [andOTP](https://github.com/andOTP/andOTP)
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
index f4c1685e..a47e6e70 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
@@ -154,6 +154,9 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
// 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 {
+ menu.findItem(R.id.menu_edit_password).isVisible =
+ recyclerAdapter.getSelectedItems(requireContext())
+ .all { it.type == PasswordItem.TYPE_CATEGORY }
return true
}
@@ -170,6 +173,11 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
requireStore().movePasswords(recyclerAdapter.getSelectedItems(requireContext()))
false
}
+ R.id.menu_edit_password -> {
+ requireStore().renameCategory(recyclerAdapter.getSelectedItems(requireContext()))
+ mode.finish()
+ false
+ }
else -> false
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
index 0e03804f..b0f6fd44 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
@@ -43,6 +43,7 @@ import com.github.ajalt.timberkt.i
import com.github.ajalt.timberkt.w
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.textfield.TextInputEditText
import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel
import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel
@@ -66,6 +67,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository.Companion.isInitialized
import com.zeapo.pwdstore.utils.PasswordRepository.PasswordSortOrder.Companion.getSortOrder
import com.zeapo.pwdstore.utils.commitChange
import com.zeapo.pwdstore.utils.listFilesRecursively
+import com.zeapo.pwdstore.utils.requestInputFocusOnView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -628,7 +630,7 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
)
.setPositiveButton(R.string.dialog_ok) { _, _ ->
launch(Dispatchers.IO) {
- movePassword(source, destinationFile)
+ moveFile(source, destinationFile)
}
}
.setNegativeButton(R.string.dialog_cancel, null)
@@ -636,7 +638,7 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
}
} else {
launch(Dispatchers.IO) {
- movePassword(source, destinationFile)
+ moveFile(source, destinationFile)
}
}
}
@@ -664,6 +666,69 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
}.launch(intent)
}
+ private fun isInsideRepository(file: File): Boolean {
+ return file.canonicalPath.contains(getRepositoryDirectory(this).canonicalPath)
+ }
+
+ 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_category_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)
+ !isInsideRepository(newCategory) -> renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo)
+ else -> lifecycleScope.launch(Dispatchers.IO) {
+ moveFile(oldCategory.file, newCategory)
+ withContext(Dispatchers.Main) {
+ commitChange(resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name))
+ }
+ }
+ }
+ }
+ .setNegativeButton(R.string.dialog_skip, null)
+ .create()
+
+ dialog.requestInputFocusOnView<TextInputEditText>(R.id.folder_name_text)
+ dialog.show()
+ }
+
+ fun renameCategory(categories: List<PasswordItem>) {
+ for (oldCategory in categories) {
+ renameCategory(oldCategory)
+ }
+ }
+
/**
* Resets navigation to the repository root and refreshes the password list accordingly.
*
@@ -736,8 +801,9 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
super.onActivityResult(requestCode, resultCode, data)
}
- private suspend fun movePassword(source: File, destinationFile: File) {
+ 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.
@@ -746,7 +812,7 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
mapOf(source to destinationFile)
}
if (!source.renameTo(destinationFile)) {
- e { "Something went wrong while moving." }
+ e { "Something went wrong while moving $source to $destinationFile." }
withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(this@PasswordStore)
.setTitle(R.string.password_move_error_title)
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt
index a5ff0bc1..2f268c56 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt
@@ -20,7 +20,7 @@ class FolderCreationDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
alertDialogBuilder.setTitle(R.string.title_create_folder)
- alertDialogBuilder.setView(R.layout.folder_creation_dialog_fragment)
+ alertDialogBuilder.setView(R.layout.folder_dialog_fragment)
alertDialogBuilder.setPositiveButton(getString(R.string.button_create)) { _, _ ->
createDirectory(requireArguments().getString(CURRENT_DIR_EXTRA)!!)
}
diff --git a/app/src/main/res/layout/folder_creation_dialog_fragment.xml b/app/src/main/res/layout/folder_dialog_fragment.xml
index bc078e64..bc078e64 100644
--- a/app/src/main/res/layout/folder_creation_dialog_fragment.xml
+++ b/app/src/main/res/layout/folder_dialog_fragment.xml
diff --git a/app/src/main/res/menu/context_pass.xml b/app/src/main/res/menu/context_pass.xml
index 41a1f705..9c76fca8 100644
--- a/app/src/main/res/menu/context_pass.xml
+++ b/app/src/main/res/menu/context_pass.xml
@@ -20,4 +20,9 @@
android:title="@string/delete"
app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/menu_edit_password"
+ android:icon="@drawable/ic_edit_white_24dp"
+ android:title="@string/edit"
+ app:showAsAction="ifRoom" />
</menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b65d84f2..58126392 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -222,6 +222,7 @@
<string name="dialog_positive">Go to Settings</string>
<string name="dialog_negative">Go back</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>
@@ -323,6 +324,11 @@
<string name="pref_show_hidden_title">Show hidden folders</string>
<string name="pref_show_hidden_summary">Include hidden directories in the password list</string>
<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_category_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>
<string name="pref_search_on_start">Open search on start</string>
<string name="pref_search_on_start_hint">Open search bar when app is launched</string>