Sort by recently used (#1031)
* Sort passwords by recently used * reformat * modified CHANGELOG.md * restore format CHANGELOG.md * added new sharedPreferences file to manage recent password history * associate timestamp when rename category * associate timestamp when rename password * reformat * Update CHANGELOG.md Co-authored-by: Fabian Henneke <FabianHenneke@users.noreply.github.com> * Update app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt Co-authored-by: Fabian Henneke <FabianHenneke@users.noreply.github.com> * Update app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt Co-authored-by: Fabian Henneke <FabianHenneke@users.noreply.github.com> * use kotlin edit extension * Add changelog entry correctly Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Save paths as Base64 hashes Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Missed it Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> Co-authored-by: Fabian Henneke <FabianHenneke@users.noreply.github.com> Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
152d86ec3a
commit
cfb42f02f5
9 changed files with 65 additions and 0 deletions
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Allow sorting by recently used
|
||||||
|
|
||||||
## [1.11.0] - 2020-08-18
|
## [1.11.0] - 2020-08-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -16,6 +16,7 @@ import android.view.animation.Animation
|
||||||
import android.view.animation.AnimationUtils
|
import android.view.animation.AnimationUtils
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
@ -32,6 +33,9 @@ import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter
|
||||||
import com.zeapo.pwdstore.ui.dialogs.ItemCreationBottomSheet
|
import com.zeapo.pwdstore.ui.dialogs.ItemCreationBottomSheet
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem
|
import com.zeapo.pwdstore.utils.PasswordItem
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
|
import com.zeapo.pwdstore.utils.base64
|
||||||
|
import com.zeapo.pwdstore.utils.getString
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
import com.zeapo.pwdstore.utils.viewBinding
|
import com.zeapo.pwdstore.utils.viewBinding
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -243,6 +247,14 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
try {
|
try {
|
||||||
listener = object : OnFragmentInteractionListener {
|
listener = object : OnFragmentInteractionListener {
|
||||||
override fun onFragmentInteraction(item: PasswordItem) {
|
override fun onFragmentInteraction(item: PasswordItem) {
|
||||||
|
if (settings.getString(PreferenceKeys.SORT_ORDER) == PasswordRepository.PasswordSortOrder.RECENTLY_USED.name) {
|
||||||
|
//save the time when password was used
|
||||||
|
val preferences = context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
|
||||||
|
preferences.edit {
|
||||||
|
putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
||||||
navigateTo(item.file)
|
navigateTo(item.file)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,7 @@ package com.zeapo.pwdstore
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ShortcutInfo.Builder
|
import android.content.pm.ShortcutInfo.Builder
|
||||||
|
@ -63,6 +64,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository.Companion.initialize
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.isInitialized
|
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.base64
|
||||||
import com.zeapo.pwdstore.utils.commitChange
|
import com.zeapo.pwdstore.utils.commitChange
|
||||||
import com.zeapo.pwdstore.utils.contains
|
import com.zeapo.pwdstore.utils.contains
|
||||||
import com.zeapo.pwdstore.utils.getString
|
import com.zeapo.pwdstore.utils.getString
|
||||||
|
@ -753,6 +755,17 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
||||||
!newCategory.isInsideRepository() -> renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo)
|
!newCategory.isInsideRepository() -> renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo)
|
||||||
else -> lifecycleScope.launch(Dispatchers.IO) {
|
else -> lifecycleScope.launch(Dispatchers.IO) {
|
||||||
moveFile(oldCategory.file, newCategory)
|
moveFile(oldCategory.file, newCategory)
|
||||||
|
|
||||||
|
//associate the new category with the last category's timestamp in history
|
||||||
|
val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
|
||||||
|
val timestamp = preference.getString(oldCategory.file.absolutePath)
|
||||||
|
if (timestamp != null) {
|
||||||
|
preference.edit {
|
||||||
|
remove(oldCategory.file.absolutePath.base64())
|
||||||
|
putString(newCategory.absolutePath.base64(), timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
commitChange(
|
commitChange(
|
||||||
resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name),
|
resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name),
|
||||||
|
|
|
@ -96,6 +96,7 @@ private fun PasswordItem.Companion.makeComparator(
|
||||||
// declare them all equal at this stage.
|
// declare them all equal at this stage.
|
||||||
PasswordRepository.PasswordSortOrder.INDEPENDENT -> Comparator<PasswordItem> { _, _ -> 0 }
|
PasswordRepository.PasswordSortOrder.INDEPENDENT -> Comparator<PasswordItem> { _, _ -> 0 }
|
||||||
PasswordRepository.PasswordSortOrder.FILE_FIRST -> compareByDescending { it.type }
|
PasswordRepository.PasswordSortOrder.FILE_FIRST -> compareByDescending { it.type }
|
||||||
|
PasswordRepository.PasswordSortOrder.RECENTLY_USED -> PasswordRepository.PasswordSortOrder.RECENTLY_USED.comparator
|
||||||
}
|
}
|
||||||
.then(compareBy(nullsLast(CaseInsensitiveComparator)) {
|
.then(compareBy(nullsLast(CaseInsensitiveComparator)) {
|
||||||
directoryStructure.getIdentifierFor(it.file)
|
directoryStructure.getIdentifierFor(it.file)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.zeapo.pwdstore.crypto
|
package com.zeapo.pwdstore.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
|
@ -14,6 +15,7 @@ import android.view.View
|
||||||
import androidx.activity.result.IntentSenderRequest
|
import androidx.activity.result.IntentSenderRequest
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
@ -30,6 +32,7 @@ import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment
|
||||||
import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
|
import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
|
import com.zeapo.pwdstore.utils.base64
|
||||||
import com.zeapo.pwdstore.utils.commitChange
|
import com.zeapo.pwdstore.utils.commitChange
|
||||||
import com.zeapo.pwdstore.utils.getString
|
import com.zeapo.pwdstore.utils.getString
|
||||||
import com.zeapo.pwdstore.utils.isInsideRepository
|
import com.zeapo.pwdstore.utils.isInsideRepository
|
||||||
|
@ -411,6 +414,17 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
||||||
return@executeApiAsync
|
return@executeApiAsync
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//associate the new password name with the last name's timestamp in history
|
||||||
|
val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
|
||||||
|
val oldFilePathHash = "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64()
|
||||||
|
val timestamp = preference.getString(oldFilePathHash)
|
||||||
|
if (timestamp != null) {
|
||||||
|
preference.edit {
|
||||||
|
remove(oldFilePathHash)
|
||||||
|
putString(file.absolutePath.base64(), timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val returnIntent = Intent()
|
val returnIntent = Intent()
|
||||||
returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path)
|
returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path)
|
||||||
returnIntent.putExtra(RETURN_EXTRA_NAME, editName)
|
returnIntent.putExtra(RETURN_EXTRA_NAME, editName)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Base64
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.autofill.AutofillManager
|
import android.view.autofill.AutofillManager
|
||||||
|
@ -42,6 +43,10 @@ fun String.splitLines(): Array<String> {
|
||||||
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.base64(): String {
|
||||||
|
return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
val Context.clipboard get() = getSystemService<ClipboardManager>()
|
val Context.clipboard get() = getSystemService<ClipboardManager>()
|
||||||
|
|
||||||
fun FragmentActivity.snackbar(
|
fun FragmentActivity.snackbar(
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
package com.zeapo.pwdstore.utils
|
package com.zeapo.pwdstore.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.zeapo.pwdstore.Application
|
import com.zeapo.pwdstore.Application
|
||||||
|
@ -31,6 +32,18 @@ open class PasswordRepository protected constructor() {
|
||||||
p1.name.compareTo(p2.name, ignoreCase = true)
|
p1.name.compareTo(p2.name, ignoreCase = true)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
RECENTLY_USED(Comparator { p1: PasswordItem, p2: PasswordItem ->
|
||||||
|
val recentHistory = Application.instance.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
|
||||||
|
val timeP1 = recentHistory.getString(p1.file.absolutePath.base64())
|
||||||
|
val timeP2 = recentHistory.getString(p2.file.absolutePath.base64())
|
||||||
|
when {
|
||||||
|
timeP1 != null && timeP2 != null -> timeP2.compareTo(timeP1)
|
||||||
|
timeP1 != null && timeP2 == null -> return@Comparator -1
|
||||||
|
timeP1 == null && timeP2 != null -> return@Comparator 1
|
||||||
|
else -> p1.name.compareTo(p2.name, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
FILE_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem ->
|
FILE_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem ->
|
||||||
(p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true)
|
(p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true)
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
<item>@string/pref_folder_first_sort_order</item>
|
<item>@string/pref_folder_first_sort_order</item>
|
||||||
<item>@string/pref_file_first_sort_order</item>
|
<item>@string/pref_file_first_sort_order</item>
|
||||||
<item>@string/pref_type_independent_sort_order</item>
|
<item>@string/pref_type_independent_sort_order</item>
|
||||||
|
<item>@string/pref_recently_used_sort_order</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="sort_order_values">
|
<string-array name="sort_order_values">
|
||||||
<item>FOLDER_FIRST</item>
|
<item>FOLDER_FIRST</item>
|
||||||
<item>FILE_FIRST</item>
|
<item>FILE_FIRST</item>
|
||||||
<item>INDEPENDENT</item>
|
<item>INDEPENDENT</item>
|
||||||
|
<item>RECENTLY_USED</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="capitalization_type_values">
|
<string-array name="capitalization_type_values">
|
||||||
<item>lowercase</item>
|
<item>lowercase</item>
|
||||||
|
|
|
@ -140,6 +140,7 @@
|
||||||
<string name="pref_folder_first_sort_order">Folders first</string>
|
<string name="pref_folder_first_sort_order">Folders first</string>
|
||||||
<string name="pref_file_first_sort_order">Files first</string>
|
<string name="pref_file_first_sort_order">Files first</string>
|
||||||
<string name="pref_type_independent_sort_order">Type independent</string>
|
<string name="pref_type_independent_sort_order">Type independent</string>
|
||||||
|
<string name="pref_recently_used_sort_order">Recently used</string>
|
||||||
<string name="pref_autofill_title">Autofill</string>
|
<string name="pref_autofill_title">Autofill</string>
|
||||||
<string name="pref_autofill_enable_title">Enable Autofill</string>
|
<string name="pref_autofill_enable_title">Enable Autofill</string>
|
||||||
<string name="pref_autofill_enable_msg">Tap OK to go to Accessibility settings. There, tap Password Store under Services then tap the switch in the top right to turn it on or off.</string>
|
<string name="pref_autofill_enable_msg">Tap OK to go to Accessibility settings. There, tap Password Store under Services then tap the switch in the top right to turn it on or off.</string>
|
||||||
|
|
Loading…
Reference in a new issue