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:
Diogenes Molinares 2020-08-20 13:53:34 +02:00 committed by GitHub
parent 152d86ec3a
commit cfb42f02f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 65 additions and 0 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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),

View file

@ -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)

View 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)

View file

@ -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(

View file

@ -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)
}); });

View file

@ -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>

View file

@ -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>