mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-09-01 14:55:19 +00:00
Scroll to files and enter folders when created (#909)
This commit is contained in:
@@ -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.
|
- 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
|
- Initial support for detecting and filling OTP fields with Autofill
|
||||||
- Importing TOTP secrets using QR codes
|
- Importing TOTP secrets using QR codes
|
||||||
|
- Navigate into newly created folders and scroll to newly created passwords
|
||||||
|
|
||||||
## [1.9.2] - 2020-06-30
|
## [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 recyclerViewStateToRestore: Parcelable? = null
|
||||||
private var actionMode: ActionMode? = null
|
private var actionMode: ActionMode? = null
|
||||||
|
private var scrollTarget: File? = null
|
||||||
|
|
||||||
private val model: SearchableRepositoryViewModel by activityViewModels()
|
private val model: SearchableRepositoryViewModel by activityViewModels()
|
||||||
private val binding by viewBinding(PasswordRecyclerViewBinding::bind)
|
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
|
// When the result is filtered, we always scroll to the top since that is where
|
||||||
// the best fuzzy match appears.
|
// the best fuzzy match appears.
|
||||||
recyclerView.scrollToPosition(0)
|
recyclerView.scrollToPosition(0)
|
||||||
|
} else if (scrollTarget != null) {
|
||||||
|
scrollTarget?.let {
|
||||||
|
recyclerView.scrollToPosition(recyclerAdapter.getPositionForFile(it))
|
||||||
|
}
|
||||||
|
scrollTarget == null
|
||||||
} else {
|
} else {
|
||||||
// When the result is not filtered and there is a saved scroll position for it,
|
// When the result is not filtered and there is a saved scroll position for it,
|
||||||
// we try to restore it.
|
// we try to restore it.
|
||||||
@@ -223,12 +229,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
|||||||
listener = object : OnFragmentInteractionListener {
|
listener = object : OnFragmentInteractionListener {
|
||||||
override fun onFragmentInteraction(item: PasswordItem) {
|
override fun onFragmentInteraction(item: PasswordItem) {
|
||||||
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
||||||
requireStore().clearSearch()
|
navigateTo(item.file)
|
||||||
model.navigateTo(
|
|
||||||
item.file,
|
|
||||||
recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState()
|
|
||||||
)
|
|
||||||
requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
|
||||||
} else {
|
} else {
|
||||||
if (requireArguments().getBoolean("matchWith", false)) {
|
if (requireArguments().getBoolean("matchWith", false)) {
|
||||||
requireStore().matchPasswordWithApp(item)
|
requireStore().matchPasswordWithApp(item)
|
||||||
@@ -272,6 +273,19 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
|||||||
|
|
||||||
fun createPassword() = requireStore().createPassword()
|
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 {
|
interface OnFragmentInteractionListener {
|
||||||
fun onFragmentInteraction(item: PasswordItem)
|
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.PasswordRepository.PasswordSortOrder.Companion.getSortOrder
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.commitChange
|
import com.zeapo.pwdstore.utils.commitChange
|
||||||
|
import com.zeapo.pwdstore.utils.contains
|
||||||
import com.zeapo.pwdstore.utils.isInsideRepository
|
import com.zeapo.pwdstore.utils.isInsideRepository
|
||||||
import com.zeapo.pwdstore.utils.listFilesRecursively
|
import com.zeapo.pwdstore.utils.listFilesRecursively
|
||||||
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
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
|
* 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,
|
* 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() {
|
fun refreshPasswordList(target: File? = null) {
|
||||||
if (model.currentDir.value?.isDirectory == true) {
|
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()
|
model.forceRefresh()
|
||||||
} else {
|
} else {
|
||||||
model.reset()
|
model.reset()
|
||||||
@@ -764,7 +772,8 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||||||
}
|
}
|
||||||
REQUEST_CODE_ENCRYPT -> {
|
REQUEST_CODE_ENCRYPT -> {
|
||||||
commitChange(resources.getString(R.string.git_commit_add_text,
|
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_INIT, NEW_REPO_BUTTON -> initializeRepositoryInfo()
|
||||||
BaseGitActivity.REQUEST_SYNC, BaseGitActivity.REQUEST_PULL -> refreshPasswordList()
|
BaseGitActivity.REQUEST_SYNC, BaseGitActivity.REQUEST_PULL -> refreshPasswordList()
|
||||||
|
@@ -447,6 +447,8 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
|
|||||||
return selectedFiles.map { it.toPasswordItem(root) }
|
return selectedFiles.map { it.toPasswordItem(root) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPositionForFile(file: File) = itemKeyProvider.getPosition(file.absolutePath)
|
||||||
|
|
||||||
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): T {
|
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): T {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val view = LayoutInflater.from(parent.context)
|
||||||
.inflate(layoutRes, parent, false)
|
.inflate(layoutRes, parent, false)
|
||||||
|
@@ -36,8 +36,9 @@ class FolderCreationDialogFragment : DialogFragment() {
|
|||||||
val dialog = requireDialog()
|
val dialog = requireDialog()
|
||||||
val materialTextView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text)
|
val materialTextView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text)
|
||||||
val folderName = materialTextView.text.toString()
|
val folderName = materialTextView.text.toString()
|
||||||
File("$currentDir/$folderName").mkdir()
|
val newFolder = File("$currentDir/$folderName")
|
||||||
(requireActivity() as PasswordStore).refreshPasswordList()
|
newFolder.mkdir()
|
||||||
|
(requireActivity() as PasswordStore).refreshPasswordList(newFolder)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -59,6 +59,23 @@ fun Activity.snackbar(
|
|||||||
|
|
||||||
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
|
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 {
|
fun Context.resolveAttribute(attr: Int): Int {
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
this.theme.resolveAttribute(attr, typedValue, true)
|
this.theme.resolveAttribute(attr, typedValue, true)
|
||||||
|
Reference in New Issue
Block a user