Merge pull request #1468 from michaelschattgen/feature/add-search-behavior

Add preference to change search behavior
This commit is contained in:
Alexander Bakker 2024-09-06 19:36:54 +02:00 committed by GitHub
commit 3bc3448b5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 136 additions and 1 deletions

View file

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

View file

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

View file

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

View file

@ -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<RecyclerView.ViewHolder>
private List<VaultEntry> _entries;
private List<VaultEntry> _shownEntries;
private List<VaultEntry> _selectedEntries;
private Collection<VaultGroup> _groups;
private Map<UUID, Integer> _usageCounts;
private Map<UUID, Long> _lastUsedTimestamps;
private VaultEntry _focusedEntry;
@ -64,6 +66,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private boolean _tapToReveal;
private int _tapToRevealTime;
private CopyBehavior _copyBehavior;
private int _searchBehaviorMask;
private Set<UUID> _groupFilter;
private SortCategory _sortCategory;
private ViewMode _viewMode;
@ -130,6 +133,8 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
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<RecyclerView.ViewHolder>
Set<UUID> 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<RecyclerView.ViewHolder>
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<UUID> 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<RecyclerView.ViewHolder>
public Map<UUID, Integer> getUsageCounts() { return _usageCounts; }
public void setGroups(Collection<VaultGroup> groups) { _groups = groups; }
public void setLastUsedTimestamps(Map<UUID, Long> lastUsedTimestamps) { _lastUsedTimestamps = lastUsedTimestamps; }
public Map<UUID, Long> getLastUsedTimestamps() { return _lastUsedTimestamps; }

View file

@ -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<VaultEntry> selectAllEntries() {
return _adapter.selectAllEntries();
}
@ -599,6 +603,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
public void setGroups(Collection<VaultGroup> groups) {
_groups = groups;
_adapter.setGroups(groups);
_groupChip.setVisibility(_groups.isEmpty() ? View.GONE : View.VISIBLE);
updateDividerDecoration();

View file

@ -125,6 +125,13 @@
<item>@string/pref_auto_lock_type_device_lock</item>
</string-array>
<string-array name="pref_search_behavior_types">
<item>@string/pref_search_behavior_type_issuer</item>
<item>@string/pref_search_behavior_type_name</item>
<item>@string/pref_search_behavior_type_note</item>
<item>@string/pref_search_behavior_type_groups</item>
</string-array>
<string-array name="export_formats">
<item>@string/export_format_aegis</item>
<item>@string/export_format_html</item>

View file

@ -107,6 +107,12 @@
<string name="pref_encryption_summary">Encrypt the vault and unlock it with a password or biometrics</string>
<string name="pref_biometrics_title">Biometric unlock</string>
<string name="pref_biometrics_summary">Allow biometric authentication to unlock the vault</string>
<string name="pref_search_behavior_summary">Search through: %s</string>
<string name="pref_search_behavior_prompt">Search in any of the following fields</string>
<string name="pref_search_behavior_type_name">Name</string>
<string name="pref_search_behavior_type_issuer">Issuer</string>
<string name="pref_search_behavior_type_note">Note</string>
<string name="pref_search_behavior_type_groups">Groups</string>
<string name="pref_set_password_title">Change password</string>
<string name="pref_set_password_summary">Set a new password which you will need to unlock your vault</string>
@ -351,6 +357,7 @@
<string name="pref_minimize_on_copy_title">Minimize on copy</string>
<string name="pref_minimize_on_copy_summary">Minimize the app after copying a token</string>
<string name="pref_copy_behavior_title">Copy tokens to the clipboard</string>
<string name="pref_search_behavior_title">Search behavior</string>
<string name="pref_pause_entry_title">Freeze tokens when tapped</string>
<string name="pref_pause_entry_summary">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\".</string>

View file

@ -8,6 +8,11 @@
android:title="@string/pref_focus_search"
android:summary="@string/pref_focus_search_summary"
app:iconSpaceReserved="false"/>
<Preference
android:defaultValue="false"
android:key="pref_search_behavior"
android:title="@string/pref_search_behavior_title"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_minimize_on_copy"