From 1c9f7971ce006d349c4cf8c1dee441c4188d5d58 Mon Sep 17 00:00:00 2001 From: Fabian Henneke Date: Wed, 1 Jul 2020 18:18:21 +0200 Subject: Scroll to files and enter folders when created (#909) --- CHANGELOG.md | 1 + .../java/com/zeapo/pwdstore/PasswordFragment.kt | 26 +++++++++++++++++----- .../main/java/com/zeapo/pwdstore/PasswordStore.kt | 17 ++++++++++---- .../pwdstore/SearchableRepositoryViewModel.kt | 2 ++ .../ui/dialogs/FolderCreationDialogFragment.kt | 5 +++-- .../java/com/zeapo/pwdstore/utils/Extensions.kt | 17 ++++++++++++++ 6 files changed, 56 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f34dfa6..17de770b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. - TOTP support is reintroduced by popular demand. HOTP continues to be unsupported and heavily discouraged. - Initial support for detecting and filling OTP fields with Autofill - Importing TOTP secrets using QR codes +- Navigate into newly created folders and scroll to newly created passwords ## [1.9.2] - 2020-06-30 diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt index ca53b320..e91df9c6 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt @@ -43,6 +43,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { 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) @@ -132,6 +133,11 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { // When the result is filtered, we always scroll to the top since that is where // the best fuzzy match appears. recyclerView.scrollToPosition(0) + } else if (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. @@ -223,12 +229,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { listener = object : OnFragmentInteractionListener { override fun onFragmentInteraction(item: PasswordItem) { if (item.type == PasswordItem.TYPE_CATEGORY) { - requireStore().clearSearch() - model.navigateTo( - item.file, - recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState() - ) - requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(true) + navigateTo(item.file) } else { if (requireArguments().getBoolean("matchWith", false)) { requireStore().matchPasswordWithApp(item) @@ -272,6 +273,19 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { fun createPassword() = requireStore().createPassword() + 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/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt index ee258b80..54fc7c97 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt @@ -67,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.PreferenceKeys import com.zeapo.pwdstore.utils.commitChange +import com.zeapo.pwdstore.utils.contains import com.zeapo.pwdstore.utils.isInsideRepository import com.zeapo.pwdstore.utils.listFilesRecursively import com.zeapo.pwdstore.utils.requestInputFocusOnView @@ -734,10 +735,17 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) { /** * 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. + * 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() { - if (model.currentDir.value?.isDirectory == true) { + fun refreshPasswordList(target: File? = null) { + if (target?.isDirectory == true && model.currentDir.value?.contains(target) == true) { + plist?.navigateTo(target) + } else if (target?.isFile == true && model.currentDir.value?.contains(target) == true) { + // Creating new passwords is handled by an activity, so we will refresh in onStart. + plist?.scrollToOnNextRefresh(target) + } else if (model.currentDir.value?.isDirectory == true) { model.forceRefresh() } else { model.reset() @@ -764,7 +772,8 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) { } REQUEST_CODE_ENCRYPT -> { commitChange(resources.getString(R.string.git_commit_add_text, - data!!.extras!!.getString("LONG_NAME"))) + data!!.extras!!.getString(PasswordCreationActivity.RETURN_EXTRA_LONG_NAME))) + refreshPasswordList(File(data.extras!!.getString(PasswordCreationActivity.RETURN_EXTRA_CREATED_FILE)!!)) } BaseGitActivity.REQUEST_INIT, NEW_REPO_BUTTON -> initializeRepositoryInfo() BaseGitActivity.REQUEST_SYNC, BaseGitActivity.REQUEST_PULL -> refreshPasswordList() diff --git a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt index 860676e4..19ba866e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt @@ -447,6 +447,8 @@ open class SearchableRepositoryAdapter( return selectedFiles.map { it.toPasswordItem(root) } } + fun getPositionForFile(file: File) = itemKeyProvider.getPosition(file.absolutePath) + final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): T { val view = LayoutInflater.from(parent.context) .inflate(layoutRes, parent, false) 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 2f268c56..3af84039 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 @@ -36,8 +36,9 @@ class FolderCreationDialogFragment : DialogFragment() { val dialog = requireDialog() val materialTextView = dialog.findViewById(R.id.folder_name_text) val folderName = materialTextView.text.toString() - File("$currentDir/$folderName").mkdir() - (requireActivity() as PasswordStore).refreshPasswordList() + val newFolder = File("$currentDir/$folderName") + newFolder.mkdir() + (requireActivity() as PasswordStore).refreshPasswordList(newFolder) dismiss() } diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt index dcede7da..6ba03743 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt @@ -59,6 +59,23 @@ fun Activity.snackbar( fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList() +/** + * Checks whether this [File] is a directory that contains [other]. + */ +fun File.contains(other: File): Boolean { + if (!isDirectory) + return false + if (!other.exists()) + return false + val relativePath = try { + other.relativeTo(this) + } catch (e: Exception) { + return false + } + // Direct containment is equivalent to the relative path being equal to the filename. + return relativePath.path == other.name +} + fun Context.resolveAttribute(attr: Int): Int { val typedValue = TypedValue() this.theme.resolveAttribute(attr, typedValue, true) -- cgit v1.2.3