From 99f613b5e6fa7aa5fb0d188b619922ff75f6639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4ttgen?= Date: Tue, 14 Apr 2026 22:28:01 +0200 Subject: [PATCH] Add configurable tap action behaviour --- .../beemdevelopment/aegis/Preferences.java | 49 ++++ .../com/beemdevelopment/aegis/TapAction.java | 21 ++ .../aegis/ui/MainActivity.java | 9 +- .../BehaviorPreferencesFragment.java | 33 +-- .../TapActionsPreferencesFragment.java | 67 ++++++ .../aegis/ui/views/EntryAdapter.java | 210 ++++++++++++------ .../aegis/ui/views/EntryListView.java | 25 ++- app/src/main/res/values/arrays.xml | 8 +- app/src/main/res/values/strings.xml | 28 ++- app/src/main/res/xml/preferences_behavior.xml | 29 ++- .../main/res/xml/preferences_tap_actions.xml | 31 +++ 11 files changed, 394 insertions(+), 116 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/TapAction.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/TapActionsPreferencesFragment.java create mode 100644 app/src/main/res/xml/preferences_tap_actions.xml diff --git a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java index 7ccda05fb4..6aac8abac7 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java +++ b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java @@ -80,6 +80,33 @@ public void migratePreferences() { _prefs.edit().remove(prefCopyOnTapKey).apply(); } + + String prefCopyBehaviorKey = "pref_current_copy_behavior"; + if (_prefs.contains(prefCopyBehaviorKey) + && !_prefs.contains("pref_single_tap_action") + && !_prefs.contains("pref_double_tap_action")) { + CopyBehavior copyBehavior = getCopyBehavior(); + + switch (copyBehavior) { + case SINGLETAP: + setSingleTapAction(TapAction.COPY); + setDoubleTapAction(TapAction.NONE); + break; + + case DOUBLETAP: + setSingleTapAction(TapAction.NONE); + setDoubleTapAction(TapAction.COPY); + break; + + case NEVER: + default: + setSingleTapAction(TapAction.NONE); + setDoubleTapAction(TapAction.NONE); + break; + } + + _prefs.edit().remove(prefCopyBehaviorKey).apply(); + } } public boolean isTapToRevealEnabled() { @@ -580,6 +607,28 @@ public void setCopyBehavior(CopyBehavior copyBehavior) { _prefs.edit().putInt("pref_current_copy_behavior", copyBehavior.ordinal()).apply(); } + public TapAction getSingleTapAction() { + int def = TapAction.COPY.ordinal(); + return TapAction.fromInteger(_prefs.getInt("pref_single_tap_action", def)); + } + + public void setSingleTapAction(TapAction tapAction) { + _prefs.edit().putInt("pref_single_tap_action", tapAction.ordinal()).apply(); + } + + public TapAction getDoubleTapAction() { + int def = TapAction.NONE.ordinal(); + return TapAction.fromInteger(_prefs.getInt("pref_double_tap_action", def)); + } + + public void setDoubleTapAction(TapAction tapAction) { + _prefs.edit().putInt("pref_double_tap_action", tapAction.ordinal()).apply(); + } + + public boolean isReserveFirstTapEnabled() { + return _prefs.getBoolean("pref_reserve_first_tap", false); + } + public boolean isMinimizeOnCopyEnabled() { return _prefs.getBoolean("pref_minimize_on_copy", false); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/TapAction.java b/app/src/main/java/com/beemdevelopment/aegis/TapAction.java new file mode 100644 index 0000000000..37b58859fa --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/TapAction.java @@ -0,0 +1,21 @@ +package com.beemdevelopment.aegis; + +public enum TapAction { + COPY, + EDIT, + NONE; + + private static TapAction[] _values; + + static { + _values = values(); + } + + public static TapAction fromInteger(int x) { + if (x < 0 || x >= _values.length) { + return NONE; + } + + return _values[x]; + } +} 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 a553fc5dfe..854df64794 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -224,12 +224,14 @@ protected void onCreate(Bundle savedInstanceState) { _entryListView.setShowNextCode(_prefs.getShowNextCode()); _entryListView.setOnlyShowNecessaryAccountNames(_prefs.onlyShowNecessaryAccountNames()); _entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled()); + _entryListView.setSingleTapAction(_prefs.getSingleTapAction()); + _entryListView.setDoubleTapAction(_prefs.getDoubleTapAction()); + _entryListView.setReserveFirstTap(_prefs.isReserveFirstTapEnabled()); _entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled()); _entryListView.setTapToReveal(_prefs.isTapToRevealEnabled()); _entryListView.setTapToRevealTime(_prefs.getTapToRevealTime()); _entryListView.setViewMode(_prefs.getCurrentViewMode()); _entryListView.setSortCategory(_prefs.getCurrentSortCategory(), false); - _entryListView.setCopyBehavior(_prefs.getCopyBehavior()); _entryListView.setSearchBehaviorMask(_prefs.getSearchBehaviorMask()); _prefGroupFilter = _prefs.getGroupFilter(); @@ -1222,6 +1224,11 @@ public void onEntryClick(VaultEntry entry) { } } + @Override + public void onEntryEdit(VaultEntry entry) { + startEditEntryActivity(entry); + } + @Override public void onSelect(VaultEntry entry) { _selectedEntries.add(entry); 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 8fa73cda9b..68d80d5543 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 @@ -6,7 +6,6 @@ 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; @@ -63,26 +62,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 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])); - copyBehaviorPreference.setOnPreferenceClickListener(preference -> { - int currentCopyBehavior1 = _prefs.getCopyBehavior().ordinal(); - - Dialogs.showSecureDialog(new MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.choose_copy_behavior)) - .setSingleChoiceItems(R.array.copy_behavior_titles, currentCopyBehavior1, (dialog, which) -> { - int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - _prefs.setCopyBehavior(CopyBehavior.fromInteger(i)); - copyBehaviorPreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.copy_behavior_titles)[i])); - dialog.dismiss(); - }) - .setNegativeButton(android.R.string.cancel, null) - .create()); - - return true; - }); - Preference entryPausePreference = requirePreference("pref_pause_entry"); entryPausePreference.setEnabled(_prefs.isTapToRevealEnabled() || _prefs.isEntryHighlightEnabled()); @@ -91,6 +70,18 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { entryPausePreference.setEnabled(_prefs.isTapToRevealEnabled() || (boolean) newValue); return true; }); + + Preference tapActions = findPreference("pref_tap_actions"); + if (tapActions != null) { + tapActions.setOnPreferenceClickListener(preference -> { + getParentFragmentManager() + .beginTransaction() + .replace(R.id.content, new TapActionsPreferencesFragment()) + .addToBackStack(null) + .commit(); + return true; + }); + } } private String getSearchBehaviorSummary() { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/TapActionsPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/TapActionsPreferencesFragment.java new file mode 100644 index 0000000000..88264c42ab --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/TapActionsPreferencesFragment.java @@ -0,0 +1,67 @@ +package com.beemdevelopment.aegis.ui.fragments.preferences; + + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.SwitchPreferenceCompat; + +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.TapAction; +import com.beemdevelopment.aegis.ui.dialogs.Dialogs; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class TapActionsPreferencesFragment extends PreferencesFragment { + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.preferences_tap_actions, rootKey); + + boolean isTapToRevealEnabled = _prefs.isTapToRevealEnabled(); + SwitchPreferenceCompat reserveFirstTap = requirePreference("pref_reserve_first_tap"); + Preference tapActionsHint = requirePreference("pref_tap_actions_hint"); + reserveFirstTap.setEnabled(isTapToRevealEnabled); + tapActionsHint.setVisible(isTapToRevealEnabled); + + Preference singleTapAction = requirePreference("pref_single_tap_action"); + singleTapAction.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.pref_tap_action_entries)[_prefs.getSingleTapAction().ordinal()])); + singleTapAction.setOnPreferenceClickListener(preference -> { + int currentSingleTapAction = _prefs.getSingleTapAction().ordinal(); + Dialogs.showSecureDialog(new MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.choose_single_tap_action)) + .setSingleChoiceItems(R.array.pref_tap_action_entries, currentSingleTapAction, (dialog, which) -> { + int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + singleTapAction.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.pref_tap_action_entries)[i])); + + _prefs.setSingleTapAction(TapAction.fromInteger(i)); + dialog.dismiss(); + }) + .setNegativeButton(android.R.string.cancel, null) + .create()); + + return true; + }); + + Preference doubleTapAction = requirePreference("pref_double_tap_action"); + doubleTapAction.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.pref_tap_action_entries)[_prefs.getDoubleTapAction().ordinal()])); + doubleTapAction.setOnPreferenceClickListener(preference -> { + int currentDoubleTapAction = _prefs.getDoubleTapAction().ordinal(); + + Dialogs.showSecureDialog(new MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.choose_double_tap_action)) + .setSingleChoiceItems(R.array.pref_tap_action_entries, currentDoubleTapAction, (dialog, which) -> { + int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + doubleTapAction.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.pref_tap_action_entries)[i])); + + _prefs.setDoubleTapAction(TapAction.fromInteger(i)); + dialog.dismiss(); + }) + .setNegativeButton(android.R.string.cancel, null) + .create()); + + return true; + }); + } +} 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 9fce5424aa..906398577f 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 @@ -20,10 +20,10 @@ import androidx.recyclerview.widget.RecyclerView; import com.beemdevelopment.aegis.AccountNamePosition; -import com.beemdevelopment.aegis.CopyBehavior; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.SortCategory; +import com.beemdevelopment.aegis.TapAction; import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.helpers.ItemTouchHelperAdapter; import com.beemdevelopment.aegis.helpers.comparators.FavoriteComparator; @@ -58,7 +58,6 @@ public class EntryAdapter extends RecyclerView.Adapter private Map _usageCounts; private Map _lastUsedTimestamps; private VaultEntry _focusedEntry; - private VaultEntry _clickedEntry; private Preferences.CodeGrouping _codeGroupSize; private AccountNamePosition _accountNamePosition; private boolean _showIcon; @@ -69,7 +68,6 @@ public class EntryAdapter extends RecyclerView.Adapter private boolean _tempHighlightEntry; private boolean _tapToReveal; private int _tapToRevealTime; - private CopyBehavior _copyBehavior; private int _searchBehaviorMask; private Set _groupFilter; private SortCategory _sortCategory; @@ -81,9 +79,24 @@ public class EntryAdapter extends RecyclerView.Adapter private Handler _doubleTapHandler; private boolean _pauseFocused; - // keeps track of the EntryHolders that are currently bound private List _holders; + private enum TapPhase { + REVEAL_OR_FOCUS, + FUNCTION, + HIDE_OR_UNFOCUS + } + + private TapAction _singleTapAction; + private TapAction _doubleTapAction; + private boolean _reserveFirstTap; + + @Nullable private VaultEntry _phaseEntry; + private TapPhase _tapPhase = TapPhase.REVEAL_OR_FOCUS; + + @Nullable private VaultEntry _pendingTapEntry; + @Nullable private Runnable _pendingSingleTapRunnable; + public EntryAdapter(EntryListView view) { _entryList = new EntryList(); _selectedEntries = new ArrayList<>(); @@ -95,6 +108,9 @@ public EntryAdapter(EntryListView view) { } public void destroy() { + _doubleTapHandler.removeCallbacksAndMessages(null); + _dimHandler.removeCallbacksAndMessages(null); + for (EntryHolder holder : _holders) { holder.destroy(); } @@ -141,7 +157,17 @@ public void setTempHighlightEntry(boolean highlightEntry) { _tempHighlightEntry = highlightEntry; } - public void setCopyBehavior(CopyBehavior copyBehavior) { _copyBehavior = copyBehavior; } + public void setSingleTapAction(TapAction action) { + _singleTapAction = action; + } + + public void setDoubleTapAction(TapAction action) { + _doubleTapAction = action; + } + + public void setReserveFirstTap(boolean reserveFirstTap) { + _reserveFirstTap = reserveFirstTap; + } public void setSearchBehaviorMask(int searchBehaviorMask) { _searchBehaviorMask = searchBehaviorMask; } @@ -170,7 +196,6 @@ public int getEntryPosition(VaultEntry entry) { } public void setEntries(List entries) { - // TODO: Move these fields to separate dedicated model for the UI for (VaultEntry entry : entries) { entry.setUsageCount(_usageCounts.containsKey(entry.getUUID()) ? _usageCounts.get(entry.getUUID()) : 0); entry.setLastUsedTimestamp(_lastUsedTimestamps.containsKey(entry.getUUID()) ? _lastUsedTimestamps.get(entry.getUUID()) : 0); @@ -200,7 +225,6 @@ private boolean isEntryFiltered(VaultEntry entry) { if (_searchFilter != null) { String[] tokens = _searchFilter.toLowerCase().split("\\s+"); - // Return true if not all tokens match at least one of the relevant fields return !Arrays.stream(tokens) .allMatch(token -> ((_searchBehaviorMask & Preferences.SEARCH_IN_ISSUER) != 0 && issuer.contains(token)) || @@ -287,9 +311,6 @@ private void replaceEntryList(EntryList newEntryList) { _entryList = newEntryList; updatePeriodUniformity(); - // This scroll position trick is required in order to not have the recycler view - // jump to some random position after a large change (like resorting entries) - // Related: https://issuetracker.google.com/issues/70149059 int scrollPos = _view.getScrollPosition(); diffRes.dispatchUpdatesTo(this); _view.scrollToPosition(scrollPos); @@ -353,8 +374,6 @@ public void onItemDismiss(int position) { @Override public void onItemDrop(int position) { - // moving entries is not allowed when a filter is applied - // footer cant be moved, nor can items be moved below it if (!_groupFilter.isEmpty() || _entryList.isPositionFooter(position) || _entryList.isPositionErrorCard(position)) { return; } @@ -365,22 +384,18 @@ public void onItemDrop(int position) { @Override public void onItemMove(int firstPosition, int secondPosition) { - // Moving entries is not allowed when a filter is applied. The footer can't be - // moved, nor can items be moved below it if (!_groupFilter.isEmpty() || _entryList.isPositionFooter(firstPosition) || _entryList.isPositionFooter(secondPosition) || _entryList.isPositionErrorCard(firstPosition) || _entryList.isPositionErrorCard(secondPosition)) { return; } - // Notify the vault about the entry position change first int firstIndex = _entryList.translateEntryPosToIndex(firstPosition); int secondIndex = _entryList.translateEntryPosToIndex(secondPosition); VaultEntry firstEntry = _entryList.getShownEntries().get(firstIndex); VaultEntry secondEntry = _entryList.getShownEntries().get(secondIndex); _view.onEntryMove(firstEntry, secondEntry); - // Then update the visual end List newEntries = new ArrayList<>(_entryList.getEntries()); CollectionUtils.move(newEntries, newEntries.indexOf(firstEntry), newEntries.indexOf(secondEntry)); replaceEntryList(new EntryList( @@ -446,7 +461,6 @@ public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) boolean showProgress = entry.getInfo() instanceof TotpInfo && ((TotpInfo) entry.getInfo()).getPeriod() != getMostFrequentPeriod(); boolean showAccountName = true; if (_onlyShowNecessaryAccountNames) { - // Only show account name when there's multiple entries found with the same issuer. showAccountName = _entryList.getEntries().stream() .filter(x -> x.getIssuer().equals(entry.getIssuer())) .count() > 1; @@ -464,43 +478,9 @@ public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) entryHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - boolean handled = false; - - if (_selectedEntries.isEmpty()) { - if (_highlightEntry || _tempHighlightEntry || _tapToReveal) { - if (_focusedEntry != null && _focusedEntry.equals(entry)) { - resetFocus(); - } else { - focusEntry(entry, _tapToRevealTime); - - // Prevent copying when singletap is set and the entry is being revealed - handled = _copyBehavior == CopyBehavior.SINGLETAP && _tapToReveal; - } - } - - switch (_copyBehavior) { - case SINGLETAP: - if (!handled) { - _view.onEntryCopy(entry); - entryHolder.animateCopyText(); - _clickedEntry = null; - } - break; - case DOUBLETAP: - _doubleTapHandler.postDelayed(() -> _clickedEntry = null, ViewConfiguration.getDoubleTapTimeout()); - - if(entry == _clickedEntry) { - _view.onEntryCopy(entry); - entryHolder.animateCopyText(); - _clickedEntry = null; - } else { - _clickedEntry = entry; - } - break; - } + if (!_selectedEntries.isEmpty()) { + cancelPendingSingleTap(); - incrementUsageCount(entry); - } else { if (_selectedEntries.contains(entry)) { _view.onDeselect(entry); removeSelectedEntry(entry); @@ -510,13 +490,54 @@ public void onClick(View v) { addSelectedEntry(entry); _view.onSelect(entry); } + return; + } + + ensurePhaseForEntry(entry); + + if (_tapPhase == TapPhase.REVEAL_OR_FOCUS && needsRevealOrHighlight(entry)) { + focusEntry(entry, _tapToRevealTime); + _tapPhase = TapPhase.FUNCTION; + + if (_reserveFirstTap) { + return; + } + } + + if (_reserveFirstTap && _tapPhase == TapPhase.HIDE_OR_UNFOCUS + && _focusedEntry != null && _focusedEntry.equals(entry)) { + cancelPendingSingleTap(); + resetFocus(); + resetTapPhase(); + return; } - if (!handled) { - _view.onEntryClick(entry); + if (_pendingSingleTapRunnable != null) { + if (_pendingTapEntry != null && _pendingTapEntry.equals(entry)) { + cancelPendingSingleTap(); + runTapAction(entryHolder, entry, _doubleTapAction); + return; + } else { + cancelPendingSingleTap(); + } } + + _pendingTapEntry = entry; + + _pendingSingleTapRunnable = new Runnable() { + @Override + public void run() { + _pendingSingleTapRunnable = null; + _pendingTapEntry = null; + + runTapAction(entryHolder, entry, _singleTapAction); + } + }; + + _doubleTapHandler.postDelayed(_pendingSingleTapRunnable, ViewConfiguration.getDoubleTapTimeout()); } }); + entryHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { @@ -534,10 +555,10 @@ public boolean onLongClick(View v) { return returnVal; } }); + entryHolder.itemView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - // Start drag if this is the only item selected if (event.getActionMasked() == MotionEvent.ACTION_MOVE && isEntryDraggable(entryHolder.getEntry())) { _view.startDrag(entryHolder); @@ -546,10 +567,10 @@ && isEntryDraggable(entryHolder.getEntry())) { return false; } }); + entryHolder.setOnRefreshClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - // this will only be called if the entry is of type HotpInfo try { ((HotpInfo) entry.getInfo()).incrementCounter(); focusEntry(entry, _tapToRevealTime); @@ -557,11 +578,7 @@ public void onClick(View v) { throw new RuntimeException(e); } - // notify the listener that the counter has been incremented - // this gives it a chance to save the vault _view.onEntryChange(entry); - - // finally, refresh the code in the UI entryHolder.refreshCode(); } }); @@ -572,6 +589,66 @@ public void onClick(View v) { } } + private void cancelPendingSingleTap() { + if (_pendingSingleTapRunnable != null) { + _doubleTapHandler.removeCallbacks(_pendingSingleTapRunnable); + _pendingSingleTapRunnable = null; + _pendingTapEntry = null; + } + } + + private void resetTapPhase() { + _phaseEntry = null; + _tapPhase = TapPhase.REVEAL_OR_FOCUS; + } + + private void ensurePhaseForEntry(VaultEntry entry) { + if (_phaseEntry == null || !_phaseEntry.equals(entry)) { + _phaseEntry = entry; + _tapPhase = TapPhase.REVEAL_OR_FOCUS; + } + } + + private boolean needsRevealOrHighlight(VaultEntry entry) { + if (_tapToReveal && (_focusedEntry == null || !_focusedEntry.equals(entry))) { + return true; + } + if ((_highlightEntry || _tempHighlightEntry) && (_focusedEntry == null || !_focusedEntry.equals(entry))) { + return true; + } + return false; + } + + private void runTapAction(EntryHolder entryHolder, VaultEntry entry, TapAction action) { + ensurePhaseForEntry(entry); + + boolean handled = false; + + switch (action) { + case COPY: + _view.onEntryCopy(entry); + entryHolder.animateCopyText(); + handled = true; + break; + + case EDIT: + _view.onEntryEdit(entry); + handled = true; + break; + + case NONE: + break; + } + + incrementUsageCount(entry); + + if (!handled) { + _view.onEntryClick(entry); + } + + _tapPhase = TapPhase.HIDE_OR_UNFOCUS; + } + private void updatePeriodUniformity() { int mostFrequentPeriod = getMostFrequentPeriod(); boolean uniform = isPeriodUniform(); @@ -676,6 +753,8 @@ private void resetFocus() { _focusedEntry = null; _tempHighlightEntry = false; + + resetTapPhase(); } private void updateDraggableStatus() { @@ -833,8 +912,6 @@ public List getShownEntries() { } public int getItemCount() { - // Always at least one item because of the footer - // Two in case there's also an error card int baseCount = 1; if (isErrorCardShown()) { baseCount++; @@ -860,9 +937,6 @@ public boolean isPositionFooter(int position) { return position == (getItemCount() - 1); } - /** - * Translates the given entry position in the recycler view, to its index in the shown entries list. - */ public int translateEntryPosToIndex(int position) { if (position == NO_POSITION) { return NO_POSITION; @@ -875,9 +949,6 @@ public int translateEntryPosToIndex(int position) { return position; } - /** - * Translates the given entry index in the shown entries list, to its position in the recycler view. - */ public int translateEntryIndexToPos(int index) { if (index == NO_POSITION) { return NO_POSITION; @@ -957,6 +1028,7 @@ public interface Listener { void onEntryDrop(VaultEntry entry); void onEntryChange(VaultEntry entry); void onEntryCopy(VaultEntry entry); + void onEntryEdit(VaultEntry entry); void onPeriodUniformityChanged(boolean uniform, int period); void onSelect(VaultEntry entry); void onDeselect(VaultEntry entry); 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 5b62cb9c2e..79ef73f704 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 @@ -33,6 +33,7 @@ import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.SortCategory; +import com.beemdevelopment.aegis.TapAction; import com.beemdevelopment.aegis.VibrationPatterns; import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.helpers.AnimationsHelper; @@ -231,10 +232,6 @@ public void setIsLongPressDragEnabled(boolean enabled) { _touchCallback.setIsLongPressDragEnabled(enabled && _adapter.isDragAndDropAllowed()); } - public void setCopyBehavior(CopyBehavior copyBehavior) { - _adapter.setCopyBehavior(copyBehavior); - } - public void setSearchBehaviorMask(int searchBehaviorMask) { _adapter.setSearchBehaviorMask(searchBehaviorMask); } @@ -358,6 +355,13 @@ public void onEntryCopy(VaultEntry entry) { } } + @Override + public void onEntryEdit(VaultEntry entry) { + if (_listener != null) { + _listener.onEntryEdit(entry); + } + } + @Override public void onSelect(VaultEntry entry) { if (_listener != null) { @@ -422,6 +426,18 @@ public void setHighlightEntry(boolean highlightEntry) { _adapter.setHighlightEntry(highlightEntry); } + public void setSingleTapAction(TapAction action) { + _adapter.setSingleTapAction(action); + } + + public void setDoubleTapAction(TapAction action) { + _adapter.setDoubleTapAction(action); + } + + public void setReserveFirstTap(boolean reserveFirstTap) { + _adapter.setReserveFirstTap(reserveFirstTap); + } + public void setPauseFocused(boolean pauseFocused) { _adapter.setPauseFocused(pauseFocused); } @@ -555,6 +571,7 @@ public interface Listener { void onEntryChange(VaultEntry entry); void onEntryCopy(VaultEntry entry); void onLongEntryClick(VaultEntry entry); + void onEntryEdit(VaultEntry entry); void onScroll(int dx, int dy); void onSelect(VaultEntry entry); void onDeselect(VaultEntry entry); diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 14f10385a1..b06bf004e4 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -170,9 +170,9 @@ @string/pref_account_name_position_below - - @string/pref_copy_behavior_never - @string/pref_copy_behavior_single_tap - @string/pref_copy_behavior_double_tap + + @string/pref_tap_action_copy + @string/pref_tap_action_edit + @string/pref_tap_action_none diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9014aa467a..f628d954bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -303,8 +303,10 @@ Select the application you\'d like to import from Select your desired theme Select your desired account name position + Select your desired single tap action + Select your desired double tap action Select your desired view mode - Select your desired copy behavior + Select your desired copy behavior An error occurred while trying to parse the file Error: File not found An error occurred while trying to read the file @@ -396,7 +398,7 @@ Allow the selection of multiple groups at the same time Minimize on copy Minimize the app after copying a token - Copy tokens to the clipboard + 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\". @@ -586,9 +588,9 @@ Groups of 3 Groups of 4 - Never - Single tap - Double tap + Never + Single tap + Double tap Hidden Next to the issuer @@ -619,4 +621,20 @@ %d item selected %d items selected + + Tap actions + Configure single tap, double tap, and first-tap reveal + + Single tap + Double tap + + Reserve first tap for reveal + When codes are hidden or a highlight is needed, the first tap only reveals/highlights + + Copy + Edit + None + + Tip + To perform an action without revealing hidden codes, assign it to Double tap. diff --git a/app/src/main/res/xml/preferences_behavior.xml b/app/src/main/res/xml/preferences_behavior.xml index 1c123164c7..2b05f6dbd5 100644 --- a/app/src/main/res/xml/preferences_behavior.xml +++ b/app/src/main/res/xml/preferences_behavior.xml @@ -2,54 +2,59 @@ + + app:iconSpaceReserved="false" /> + + app:iconSpaceReserved="false" /> + + app:iconSpaceReserved="false" /> + android:key="pref_tap_actions" + android:title="@string/pref_tap_actions_title" + android:summary="@string/pref_tap_actions_summary" + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + - + app:iconSpaceReserved="false" /> + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_tap_actions.xml b/app/src/main/res/xml/preferences_tap_actions.xml new file mode 100644 index 0000000000..1147b3b956 --- /dev/null +++ b/app/src/main/res/xml/preferences_tap_actions.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file