Scroll to files and enter folders when created (#909)
This commit is contained in:
parent
c5a93b8b81
commit
1c9f7971ce
6 changed files with 56 additions and 12 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -447,6 +447,8 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
|
|||
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)
|
||||
|
|
|
@ -36,8 +36,9 @@ class FolderCreationDialogFragment : DialogFragment() {
|
|||
val dialog = requireDialog()
|
||||
val materialTextView = dialog.findViewById<TextInputEditText>(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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue