diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e5b01b3..d9346f7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Allow sorting by recently used + ## [1.11.0] - 2020-08-18 ### Added diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt index 6657afafd..9e50ad2b9 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt @@ -16,6 +16,7 @@ import android.view.animation.Animation import android.view.animation.AnimationUtils import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.appcompat.view.ActionMode +import androidx.core.content.edit import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels 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.utils.PasswordItem 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.viewBinding import java.io.File @@ -243,6 +247,14 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { try { listener = object : OnFragmentInteractionListener { 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) { navigateTo(item.file) } else { diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt index 3ebd35b8f..fe1ab0856 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt @@ -6,6 +6,7 @@ package com.zeapo.pwdstore import android.Manifest import android.annotation.SuppressLint +import android.content.Context import android.content.Intent import android.content.pm.PackageManager 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.PasswordSortOrder.Companion.getSortOrder import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.base64 import com.zeapo.pwdstore.utils.commitChange import com.zeapo.pwdstore.utils.contains import com.zeapo.pwdstore.utils.getString @@ -753,6 +755,17 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) { !newCategory.isInsideRepository() -> renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo) else -> lifecycleScope.launch(Dispatchers.IO) { 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) { commitChange( resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name), diff --git a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt index 7ffa71a3f..0509a3dd0 100644 --- a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt @@ -96,6 +96,7 @@ private fun PasswordItem.Companion.makeComparator( // declare them all equal at this stage. PasswordRepository.PasswordSortOrder.INDEPENDENT -> Comparator { _, _ -> 0 } PasswordRepository.PasswordSortOrder.FILE_FIRST -> compareByDescending { it.type } + PasswordRepository.PasswordSortOrder.RECENTLY_USED -> PasswordRepository.PasswordSortOrder.RECENTLY_USED.comparator } .then(compareBy(nullsLast(CaseInsensitiveComparator)) { directoryStructure.getIdentifierFor(it.file) diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt index 1376dd04a..c5d3d70a5 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -5,6 +5,7 @@ package com.zeapo.pwdstore.crypto +import android.content.Context import android.content.Intent import android.os.Bundle import android.text.InputType @@ -14,6 +15,7 @@ import android.view.View import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult +import androidx.core.content.edit import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged 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.utils.PasswordRepository import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.base64 import com.zeapo.pwdstore.utils.commitChange import com.zeapo.pwdstore.utils.getString import com.zeapo.pwdstore.utils.isInsideRepository @@ -411,6 +414,17 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB 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() returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path) returnIntent.putExtra(RETURN_EXTRA_NAME, editName) 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 96bf1b7e5..561b8d999 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt @@ -9,6 +9,7 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Build +import android.util.Base64 import android.util.TypedValue import android.view.View import android.view.autofill.AutofillManager @@ -42,6 +43,10 @@ fun String.splitLines(): Array { return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() } +fun String.base64(): String { + return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP) +} + val Context.clipboard get() = getSystemService() fun FragmentActivity.snackbar( diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt index 8a49f0e3e..45453d4f2 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt @@ -4,6 +4,7 @@ */ package com.zeapo.pwdstore.utils +import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import com.zeapo.pwdstore.Application @@ -31,6 +32,18 @@ open class PasswordRepository protected constructor() { 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 -> (p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true) }); diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6b5edf6c7..2b320f31a 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -8,11 +8,13 @@ @string/pref_folder_first_sort_order @string/pref_file_first_sort_order @string/pref_type_independent_sort_order + @string/pref_recently_used_sort_order FOLDER_FIRST FILE_FIRST INDEPENDENT + RECENTLY_USED lowercase diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe2a3488b..ff1d52386 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,6 +140,7 @@ Folders first Files first Type independent + Recently used Autofill Enable Autofill 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.