diff options
author | Diogenes Molinares <amolinares19@gmail.com> | 2020-06-18 14:07:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-18 14:07:26 +0200 |
commit | 23b488a8eb69c99d5337538cc17690d2b637b9be (patch) | |
tree | dea8de23cb6f9e4e9134e75a8ee917080b3d38aa | |
parent | 33b3f54921bba7549933c6f889fbe7ab63b61b7c (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.md | 2 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt | 8 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt | 74 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt | 2 | ||||
-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.xml | 5 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 6 |
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> |