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 /app/src/main/java/com | |
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>
Diffstat (limited to 'app/src/main/java/com')
3 files changed, 79 insertions, 5 deletions
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)!!) } |