From 3425256c29624791463da5b116b18a4f8f9ec081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4ttgen?= Date: Wed, 28 Aug 2024 00:20:08 +0200 Subject: [PATCH] Add preference to switch search behavior --- .../beemdevelopment/aegis/Preferences.java | 27 ++++++++ .../aegis/ui/MainActivity.java | 1 + .../BehaviorPreferencesFragment.java | 65 +++++++++++++++++++ .../aegis/ui/views/EntryAdapter.java | 20 +++++- .../aegis/ui/views/EntryListView.java | 5 ++ app/src/main/res/values/arrays.xml | 7 ++ app/src/main/res/values/strings.xml | 7 ++ app/src/main/res/xml/preferences_behavior.xml | 5 ++ 8 files changed, 136 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java index 0ccf77ee..c3077639 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java +++ b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java @@ -33,6 +33,12 @@ public class Preferences { public static final int AUTO_LOCK_ON_BACK_BUTTON = 1 << 1; public static final int AUTO_LOCK_ON_MINIMIZE = 1 << 2; public static final int AUTO_LOCK_ON_DEVICE_LOCK = 1 << 3; + + public static final int SEARCH_IN_ISSUER = 1 << 0; + public static final int SEARCH_IN_NAME = 1 << 1; + public static final int SEARCH_IN_NOTE = 1 << 2; + public static final int SEARCH_IN_GROUPS = 1 << 3; + public static final int BACKUPS_VERSIONS_INFINITE = -1; public static final int[] AUTO_LOCK_SETTINGS = { @@ -41,6 +47,13 @@ public class Preferences { AUTO_LOCK_ON_DEVICE_LOCK }; + public static final int[] SEARCH_BEHAVIOR_SETTINGS = { + SEARCH_IN_ISSUER, + SEARCH_IN_NAME, + SEARCH_IN_NOTE, + SEARCH_IN_GROUPS + }; + private SharedPreferences _prefs; public Preferences(Context context) { @@ -163,6 +176,20 @@ public class Preferences { return _prefs.getInt("pref_auto_lock_mask", def); } + public int getSearchBehaviorMask() { + final int def = SEARCH_IN_ISSUER | SEARCH_IN_NAME; + + return _prefs.getInt("pref_search_behavior_mask", def); + } + + public boolean isSearchBehaviorTypeEnabled(int searchBehaviorType) { + return (getSearchBehaviorMask() & searchBehaviorType) == searchBehaviorType; + } + + public void setSearchBehaviorMask(int searchBehavior) { + _prefs.edit().putInt("pref_search_behavior_mask", searchBehavior).apply(); + } + public boolean isAutoLockEnabled() { return getAutoLockMask() != AUTO_LOCK_OFF; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index 5411162d..82680e4f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -195,6 +195,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _entryListView.setSortCategory(_prefs.getCurrentSortCategory(), false); _entryListView.setViewMode(_prefs.getCurrentViewMode()); _entryListView.setCopyBehavior(_prefs.getCopyBehavior()); + _entryListView.setSearchBehaviorMask(_prefs.getSearchBehaviorMask()); _entryListView.setPrefGroupFilter(_prefs.getGroupFilter()); FloatingActionButton fab = findViewById(R.id.fab); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java index 679021e7..8fa73cda 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java @@ -1,11 +1,13 @@ package com.beemdevelopment.aegis.ui.fragments.preferences; import android.os.Bundle; +import android.widget.Button; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import com.beemdevelopment.aegis.CopyBehavior; +import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -16,6 +18,51 @@ public class BehaviorPreferencesFragment extends PreferencesFragment { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.preferences_behavior); + Preference currentSearchBehaviorPreference = requirePreference("pref_search_behavior"); + currentSearchBehaviorPreference.setSummary(getSearchBehaviorSummary()); + currentSearchBehaviorPreference.setOnPreferenceClickListener((preference) -> { + final int[] items = Preferences.SEARCH_BEHAVIOR_SETTINGS; + final String[] textItems = getResources().getStringArray(R.array.pref_search_behavior_types); + final boolean[] checkedItems = new boolean[items.length]; + for (int i = 0; i < items.length; i++) { + checkedItems[i] = _prefs.isSearchBehaviorTypeEnabled(items[i]); + } + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.pref_search_behavior_prompt) + .setMultiChoiceItems(textItems, checkedItems, (dialog, index, isChecked) -> { + checkedItems[index] = isChecked; + + boolean containsAtLeastOneCheckedItem = false; + for(boolean b: checkedItems) { + if (b) { + containsAtLeastOneCheckedItem = true; + break; + } + } + + AlertDialog alertDialog = (AlertDialog) dialog; + Button positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + + positiveButton.setEnabled(containsAtLeastOneCheckedItem); + }) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + int searchBehavior = 0; + for (int i = 0; i < checkedItems.length; i++) { + if (checkedItems[i]) { + searchBehavior |= items[i]; + } + } + + _prefs.setSearchBehaviorMask(searchBehavior); + currentSearchBehaviorPreference.setSummary(getSearchBehaviorSummary()); + }) + .setNegativeButton(android.R.string.cancel, null); + + Dialogs.showSecureDialog(builder.create()); + return true; + }); + int currentCopyBehavior = _prefs.getCopyBehavior().ordinal(); Preference copyBehaviorPreference = requirePreference("pref_copy_behavior"); copyBehaviorPreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.copy_behavior_titles)[currentCopyBehavior])); @@ -45,4 +92,22 @@ public class BehaviorPreferencesFragment extends PreferencesFragment { return true; }); } + + private String getSearchBehaviorSummary() { + final int[] settings = Preferences.SEARCH_BEHAVIOR_SETTINGS; + final String[] descriptions = getResources().getStringArray(R.array.pref_search_behavior_types); + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < settings.length; i++) { + if (_prefs.isSearchBehaviorTypeEnabled(settings[i])) { + if (builder.length() != 0) { + builder.append(", "); + } + + builder.append(descriptions[i].toLowerCase()); + } + } + + return getString(R.string.pref_search_behavior_summary, builder.toString()); + } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java index 0ca0859b..56f521e2 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java @@ -32,6 +32,7 @@ import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.ui.models.ErrorCardInfo; import com.beemdevelopment.aegis.util.CollectionUtils; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.beemdevelopment.aegis.vault.VaultGroup; import java.util.ArrayList; import java.util.Collection; @@ -51,6 +52,7 @@ public class EntryAdapter extends RecyclerView.Adapter private List _entries; private List _shownEntries; private List _selectedEntries; + private Collection _groups; private Map _usageCounts; private Map _lastUsedTimestamps; private VaultEntry _focusedEntry; @@ -64,6 +66,7 @@ public class EntryAdapter extends RecyclerView.Adapter private boolean _tapToReveal; private int _tapToRevealTime; private CopyBehavior _copyBehavior; + private int _searchBehaviorMask; private Set _groupFilter; private SortCategory _sortCategory; private ViewMode _viewMode; @@ -130,6 +133,8 @@ public class EntryAdapter extends RecyclerView.Adapter public void setCopyBehavior(CopyBehavior copyBehavior) { _copyBehavior = copyBehavior; } + public void setSearchBehaviorMask(int searchBehaviorMask) { _searchBehaviorMask = searchBehaviorMask; } + public void setPauseFocused(boolean pauseFocused) { _pauseFocused = pauseFocused; } @@ -308,6 +313,7 @@ public class EntryAdapter extends RecyclerView.Adapter Set groups = entry.getGroups(); String issuer = entry.getIssuer().toLowerCase(); String name = entry.getName().toLowerCase(); + String note = entry.getNote().toLowerCase(); if (!_groupFilter.isEmpty()) { if (groups.isEmpty() && !_groupFilter.contains(null)) { @@ -322,7 +328,17 @@ public class EntryAdapter extends RecyclerView.Adapter return false; } - return !issuer.contains(_searchFilter) && !name.contains(_searchFilter); + return ((_searchBehaviorMask & Preferences.SEARCH_IN_ISSUER) == 0 || !issuer.contains(_searchFilter)) + && ((_searchBehaviorMask & Preferences.SEARCH_IN_NAME) == 0 || !name.contains(_searchFilter)) + && ((_searchBehaviorMask & Preferences.SEARCH_IN_NOTE) == 0 || !note.contains(_searchFilter)) + && ((_searchBehaviorMask & Preferences.SEARCH_IN_GROUPS) == 0 || !doesAnyGroupMatchSearchFilter(entry.getGroups(), _searchFilter)); + } + + private boolean doesAnyGroupMatchSearchFilter(Set entryGroupUUIDs, String searchFilter) { + return _groups.stream() + .filter(group -> entryGroupUUIDs.contains(group.getUUID())) + .map(VaultGroup::getName) + .anyMatch(groupName -> groupName.toLowerCase().contains(searchFilter.toLowerCase())); } public void refresh(boolean hard) { @@ -411,6 +427,8 @@ public class EntryAdapter extends RecyclerView.Adapter public Map getUsageCounts() { return _usageCounts; } + public void setGroups(Collection groups) { _groups = groups; } + public void setLastUsedTimestamps(Map lastUsedTimestamps) { _lastUsedTimestamps = lastUsedTimestamps; } public Map getLastUsedTimestamps() { return _lastUsedTimestamps; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java index f2ad7089..6bd4fb68 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java @@ -222,6 +222,10 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { _adapter.setCopyBehavior(copyBehavior); } + public void setSearchBehaviorMask(int searchBehaviorMask) { + _adapter.setSearchBehaviorMask(searchBehaviorMask); + } + public List selectAllEntries() { return _adapter.selectAllEntries(); } @@ -599,6 +603,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { public void setGroups(Collection groups) { _groups = groups; + _adapter.setGroups(groups); _groupChip.setVisibility(_groups.isEmpty() ? View.GONE : View.VISIBLE); updateDividerDecoration(); diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 2403d9f0..17ac962e 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -125,6 +125,13 @@ @string/pref_auto_lock_type_device_lock + + @string/pref_search_behavior_type_issuer + @string/pref_search_behavior_type_name + @string/pref_search_behavior_type_note + @string/pref_search_behavior_type_groups + + @string/export_format_aegis @string/export_format_html diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc455a25..4e027399 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,12 @@ Encrypt the vault and unlock it with a password or biometrics Biometric unlock Allow biometric authentication to unlock the vault + Search through: %s + Search in any of the following fields + Name + Issuer + Note + Groups Change password Set a new password which you will need to unlock your vault @@ -350,6 +356,7 @@ Minimize on copy Minimize the app after copying a token Copy tokens to the clipboard + Search behavior Freeze tokens when tapped Pause automatic refresh of tokens by tapping them. Tokens will not update as long as they are focused. Requires \"Highlight tokens when tapped\" or \"Tap to reveal\". diff --git a/app/src/main/res/xml/preferences_behavior.xml b/app/src/main/res/xml/preferences_behavior.xml index f5fd3535..ac2fa0d2 100644 --- a/app/src/main/res/xml/preferences_behavior.xml +++ b/app/src/main/res/xml/preferences_behavior.xml @@ -8,6 +8,11 @@ android:title="@string/pref_focus_search" android:summary="@string/pref_focus_search_summary" app:iconSpaceReserved="false"/> +