mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-23 23:39:14 +00:00
Use DiffUtil for the RecyclerView of the entry list
Gets rid of all of the custom logic we had for notifying the RecyclerView about changes in the entry list. This will allow for more simplifications in the future around non-persisted changes to state in the entry list. A neat side effect is that any filtering/ordering changes in the entry list are now also animated: https://alexbakker.me/u/4a4ie5yzpj.mp4 This touches the fundamentals of the entry list, so lots of careful testing required.
This commit is contained in:
parent
08d900c0c0
commit
9131cae944
6 changed files with 386 additions and 352 deletions
|
@ -69,7 +69,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||
int swipeFlags = 0;
|
||||
if (adapter.isPositionFooter(position)
|
||||
|| adapter.isPositionErrorCard(position)
|
||||
|| adapter.getEntryAtPos(position) != _selectedEntry
|
||||
|| adapter.getEntryAtPosition(position) != _selectedEntry
|
||||
|| !isLongPressDragEnabled()) {
|
||||
return makeMovementFlags(0, swipeFlags);
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
if (activityResult.getResultCode() != RESULT_OK || activityResult.getData() == null) {
|
||||
return;
|
||||
}
|
||||
onAssignIconsResult(activityResult.getData());
|
||||
onAssignIconsResult();
|
||||
});
|
||||
|
||||
private final ActivityResultLauncher<Intent> preferenceResultLauncher =
|
||||
|
@ -160,7 +160,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
if (activityResult.getResultCode() != RESULT_OK || activityResult.getData() == null) {
|
||||
return;
|
||||
}
|
||||
onEditEntryResult(activityResult.getData());
|
||||
onEditEntryResult();
|
||||
});
|
||||
|
||||
private final ActivityResultLauncher<Intent> addEntryResultLauncher =
|
||||
|
@ -255,13 +255,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
_prefGroupFilter = null;
|
||||
if (!groupFilter.isEmpty()) {
|
||||
_groupFilter = groupFilter;
|
||||
_entryListView.setGroupFilter(groupFilter, false);
|
||||
_entryListView.setGroupFilter(groupFilter);
|
||||
}
|
||||
} else if (_groupFilter != null) {
|
||||
Set<UUID> groupFilter = cleanGroupFilter(_groupFilter);
|
||||
if (!_groupFilter.equals(groupFilter)) {
|
||||
_groupFilter = groupFilter;
|
||||
_entryListView.setGroupFilter(groupFilter, true);
|
||||
_entryListView.setGroupFilter(groupFilter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
if (!isChecked) {
|
||||
group1.setChecked(false);
|
||||
_groupFilter = groupFilter;
|
||||
_entryListView.setGroupFilter(groupFilter, false);
|
||||
_entryListView.setGroupFilter(groupFilter);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -328,7 +328,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
}
|
||||
|
||||
_groupFilter = groupFilter;
|
||||
_entryListView.setGroupFilter(groupFilter, false);
|
||||
_entryListView.setGroupFilter(groupFilter);
|
||||
});
|
||||
|
||||
chipGroup.addView(chip);
|
||||
|
@ -573,31 +573,20 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
if (_loaded) {
|
||||
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
|
||||
VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID);
|
||||
_entryListView.addEntry(entry, true);
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
_entryListView.onEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEditEntryResult(Intent data) {
|
||||
private void onEditEntryResult() {
|
||||
if (_loaded) {
|
||||
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
|
||||
|
||||
if (data.getBooleanExtra("delete", false)) {
|
||||
_entryListView.removeEntry(entryUUID);
|
||||
} else {
|
||||
VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID);
|
||||
_entryListView.replaceEntry(entryUUID, entry);
|
||||
}
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
}
|
||||
}
|
||||
|
||||
private void onAssignIconsResult(Intent data) {
|
||||
private void onAssignIconsResult() {
|
||||
if (_loaded) {
|
||||
ArrayList<UUID> entryUUIDs = (ArrayList<UUID>) data.getSerializableExtra("entryUUIDs");
|
||||
|
||||
for (UUID entryUUID: entryUUIDs) {
|
||||
VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID);
|
||||
_entryListView.replaceEntry(entryUUID, entry);
|
||||
}
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -695,14 +684,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
if (entries.size() == 1) {
|
||||
startEditEntryActivityForNew(entries.get(0));
|
||||
} else if (entries.size() > 1) {
|
||||
for (VaultEntry entry: entries) {
|
||||
_vaultManager.getVault().addEntry(entry);
|
||||
_entryListView.addEntry(entry);
|
||||
}
|
||||
|
||||
if (saveAndBackupVault()) {
|
||||
Toast.makeText(this, getResources().getQuantityString(R.plurals.added_new_entries, entries.size(), entries.size()), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -925,15 +911,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
updateErrorCard();
|
||||
}
|
||||
|
||||
private void deleteEntries(List<VaultEntry> entries) {
|
||||
for (VaultEntry entry: entries) {
|
||||
VaultEntry oldEntry = _vaultManager.getVault().removeEntry(entry);
|
||||
_entryListView.removeEntry(oldEntry);
|
||||
}
|
||||
|
||||
saveAndBackupVault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
_menu = menu;
|
||||
|
@ -1063,7 +1040,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
setGroups(_vaultManager.getVault().getUsedGroups());
|
||||
_entryListView.setUsageCounts(_prefs.getUsageCounts());
|
||||
_entryListView.setLastUsedTimestamps(_prefs.getLastUsedTimestamps());
|
||||
_entryListView.addEntries(_vaultManager.getVault().getEntries());
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
if (!_isRecreated) {
|
||||
_entryListView.runEntriesAnimation();
|
||||
}
|
||||
|
@ -1291,6 +1268,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean saveAndBackupVault() {
|
||||
boolean res = super.saveAndBackupVault();
|
||||
updateErrorCard();
|
||||
return res;
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private void copyEntryCode(VaultEntry entry) {
|
||||
String otp;
|
||||
|
@ -1387,12 +1371,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
mode.finish();
|
||||
} else if (itemId == R.id.action_toggle_favorite) {
|
||||
for (VaultEntry entry : _selectedEntries) {
|
||||
entry.setIsFavorite(!entry.isFavorite());
|
||||
_entryListView.replaceEntry(entry.getUUID(), entry);
|
||||
_vaultManager.getVault().editEntry(entry, newEntry -> {
|
||||
newEntry.setIsFavorite(!newEntry.isFavorite());
|
||||
});
|
||||
}
|
||||
_entryListView.refresh(true);
|
||||
|
||||
saveAndBackupVault();
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
mode.finish();
|
||||
} else if (itemId == R.id.action_share_qr) {
|
||||
Intent intent = new Intent(getBaseContext(), TransferEntriesActivity.class);
|
||||
|
@ -1410,8 +1395,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
mode.finish();
|
||||
} else if (itemId == R.id.action_delete) {
|
||||
Dialogs.showDeleteEntriesDialog(MainActivity.this, _selectedEntries, (d, which) -> {
|
||||
deleteEntries(_selectedEntries);
|
||||
for (VaultEntry entry : _selectedEntries) {
|
||||
_vaultManager.getVault().removeEntry(entry);
|
||||
}
|
||||
saveAndBackupVault();
|
||||
_entryListView.setGroups(_vaultManager.getVault().getUsedGroups());
|
||||
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||
mode.finish();
|
||||
});
|
||||
} else if (itemId == R.id.action_select_all) {
|
||||
|
|
|
@ -2,6 +2,10 @@ package com.beemdevelopment.aegis.ui.models;
|
|||
|
||||
import android.view.View;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ErrorCardInfo {
|
||||
private final String _message;
|
||||
private final View.OnClickListener _listener;
|
||||
|
@ -18,4 +22,23 @@ public class ErrorCardInfo {
|
|||
public View.OnClickListener getListener() {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCode.fromString(_message).asInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ErrorCardInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This equality check purposefully ignores the onclick listener
|
||||
ErrorCardInfo info = (ErrorCardInfo) o;
|
||||
return Objects.equals(getMessage(), info.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import android.view.ViewGroup;
|
|||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.AccountNamePosition;
|
||||
|
@ -49,8 +51,7 @@ import java.util.UUID;
|
|||
|
||||
public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemTouchHelperAdapter {
|
||||
private EntryListView _view;
|
||||
private List<VaultEntry> _entries;
|
||||
private List<VaultEntry> _shownEntries;
|
||||
private EntryList _entryList;
|
||||
private List<VaultEntry> _selectedEntries;
|
||||
private Collection<VaultGroup> _groups;
|
||||
private Map<UUID, Integer> _usageCounts;
|
||||
|
@ -77,14 +78,12 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
private Handler _dimHandler;
|
||||
private Handler _doubleTapHandler;
|
||||
private boolean _pauseFocused;
|
||||
private ErrorCardInfo _errorCardInfo;
|
||||
|
||||
// keeps track of the EntryHolders that are currently bound
|
||||
private List<EntryHolder> _holders;
|
||||
|
||||
public EntryAdapter(EntryListView view) {
|
||||
_entries = new ArrayList<>();
|
||||
_shownEntries = new ArrayList<>();
|
||||
_entryList = new EntryList();
|
||||
_selectedEntries = new ArrayList<>();
|
||||
_groupFilter = new TreeSet<>();
|
||||
_holders = new ArrayList<>();
|
||||
|
@ -145,173 +144,45 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
|
||||
public void setErrorCardInfo(ErrorCardInfo info) {
|
||||
ErrorCardInfo oldInfo = _errorCardInfo;
|
||||
_errorCardInfo = info;
|
||||
|
||||
if (oldInfo == null && info != null) {
|
||||
notifyItemInserted(0);
|
||||
} else if (oldInfo != null && info == null) {
|
||||
notifyItemRemoved(0);
|
||||
} else {
|
||||
notifyItemChanged(0);
|
||||
}
|
||||
if (Objects.equals(info, _entryList.getErrorCardInfo())) {
|
||||
return;
|
||||
}
|
||||
|
||||
public VaultEntry getEntryAtPos(int position) {
|
||||
return _shownEntries.get(translateEntryPosToIndex(position));
|
||||
replaceEntryList(new EntryList(
|
||||
_entryList.getEntries(),
|
||||
_entryList.getShownEntries(),
|
||||
info
|
||||
));
|
||||
}
|
||||
|
||||
public int addEntry(VaultEntry entry) {
|
||||
_entries.add(entry);
|
||||
if (isEntryFiltered(entry)) {
|
||||
return -1;
|
||||
public VaultEntry getEntryAtPosition(int position) {
|
||||
return _entryList.getShownEntries().get(_entryList.translateEntryPosToIndex(position));
|
||||
}
|
||||
|
||||
int position = -1;
|
||||
Comparator<VaultEntry> comparator = _sortCategory.getComparator();
|
||||
if (comparator != null) {
|
||||
// insert the entry in the correct order
|
||||
// note: this assumes that _shownEntries has already been sorted
|
||||
for (int i = getShownFavoritesCount(); i < _shownEntries.size(); i++) {
|
||||
if (comparator.compare(_shownEntries.get(i), entry) > 0) {
|
||||
_shownEntries.add(i, entry);
|
||||
position = translateEntryIndexToPos(i);
|
||||
notifyItemInserted(position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public int getEntryPosition(VaultEntry entry) {
|
||||
return _entryList.translateEntryIndexToPos(_entryList.getShownEntries().indexOf(entry));
|
||||
}
|
||||
|
||||
if (position < 0) {
|
||||
_shownEntries.add(entry);
|
||||
|
||||
position = translateEntryIndexToPos(getShownEntriesCount() - 1);
|
||||
if (position == 0) {
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
}
|
||||
|
||||
_view.onListChange();
|
||||
checkPeriodUniformity();
|
||||
updateFooter();
|
||||
return position;
|
||||
}
|
||||
|
||||
public void addEntries(Collection<VaultEntry> entries) {
|
||||
for (VaultEntry entry: entries) {
|
||||
public void setEntries(List<VaultEntry> 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);
|
||||
}
|
||||
|
||||
_entries.addAll(entries);
|
||||
updateShownEntries();
|
||||
checkPeriodUniformity(true);
|
||||
}
|
||||
|
||||
public void removeEntry(VaultEntry entry) {
|
||||
_entries.remove(entry);
|
||||
|
||||
if (_shownEntries.contains(entry)) {
|
||||
int index = _shownEntries.indexOf(entry);
|
||||
_shownEntries.remove(index);
|
||||
|
||||
int position = translateEntryIndexToPos(index);
|
||||
notifyItemRemoved(position);
|
||||
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
_view.onListChange();
|
||||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
public void removeEntry(UUID uuid) {
|
||||
VaultEntry entry = getEntryByUUID(uuid);
|
||||
removeEntry(entry);
|
||||
replaceEntryList(new EntryList(
|
||||
entries,
|
||||
calculateShownEntries(entries),
|
||||
_entryList.getErrorCardInfo()
|
||||
));
|
||||
}
|
||||
|
||||
public void clearEntries() {
|
||||
_entries.clear();
|
||||
_shownEntries.clear();
|
||||
notifyDataSetChanged();
|
||||
checkPeriodUniformity();
|
||||
replaceEntryList(new EntryList());
|
||||
}
|
||||
|
||||
public void replaceEntry(UUID uuid, VaultEntry newEntry) {
|
||||
VaultEntry oldEntry = getEntryByUUID(uuid);
|
||||
_entries.set(_entries.indexOf(oldEntry), newEntry);
|
||||
|
||||
if (_shownEntries.contains(oldEntry)) {
|
||||
int index = _shownEntries.indexOf(oldEntry);
|
||||
int position = translateEntryIndexToPos(index);
|
||||
if (isEntryFiltered(newEntry)) {
|
||||
_shownEntries.remove(index);
|
||||
notifyItemRemoved(position);
|
||||
} else {
|
||||
_shownEntries.set(index, newEntry);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
sortShownEntries();
|
||||
int newIndex = _shownEntries.indexOf(newEntry);
|
||||
int newPosition = translateEntryIndexToPos(newIndex);
|
||||
if (newPosition != NO_POSITION && position != newPosition) {
|
||||
notifyItemMoved(position, newPosition);
|
||||
}
|
||||
} else if (!isEntryFiltered(newEntry)) {
|
||||
// NOTE: This logic is wrong, because sorting is not taken into account. This code
|
||||
// path is currently never hit though, because it is not possible to edit an entry
|
||||
// that is not shown.
|
||||
_shownEntries.add(newEntry);
|
||||
|
||||
int position = getItemCount() - 1;
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
|
||||
checkPeriodUniformity();
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
private VaultEntry getEntryByUUID(UUID uuid) {
|
||||
for (VaultEntry entry : _entries) {
|
||||
if (entry.getUUID().equals(uuid)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
if (isErrorCardShown()) {
|
||||
position -= 1;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given entry index in the shown entries list, to its position in the recycler view.
|
||||
*/
|
||||
private int translateEntryIndexToPos(int index) {
|
||||
if (index == NO_POSITION) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
if (isErrorCardShown()) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return index;
|
||||
return _entryList.translateEntryPosToIndex(position);
|
||||
}
|
||||
|
||||
private boolean isEntryFiltered(VaultEntry entry) {
|
||||
|
@ -348,7 +219,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
public void refresh(boolean hard) {
|
||||
if (hard) {
|
||||
updateShownEntries();
|
||||
refreshEntryList();
|
||||
} else {
|
||||
for (EntryHolder holder : _holders) {
|
||||
holder.refresh();
|
||||
|
@ -363,8 +234,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
|
||||
_groupFilter = groups;
|
||||
updateShownEntries();
|
||||
checkPeriodUniformity();
|
||||
refreshEntryList();
|
||||
}
|
||||
|
||||
public void setSortCategory(SortCategory category, boolean apply) {
|
||||
|
@ -374,7 +244,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
_sortCategory = category;
|
||||
if (apply) {
|
||||
updateShownEntries();
|
||||
refreshEntryList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,25 +253,59 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
|
||||
public void setSearchFilter(String search) {
|
||||
_searchFilter = (search != null && !search.isEmpty()) ? search.toLowerCase().trim() : null;
|
||||
updateShownEntries();
|
||||
}
|
||||
String newSearchFilter = (search != null && !search.isEmpty())
|
||||
? search.toLowerCase().trim() : null;
|
||||
|
||||
private void updateShownEntries() {
|
||||
// clear the list of shown entries first
|
||||
_shownEntries.clear();
|
||||
|
||||
// add entries back that are not filtered out
|
||||
for (VaultEntry entry : _entries) {
|
||||
if (!isEntryFiltered(entry)) {
|
||||
_shownEntries.add(entry);
|
||||
if (!Objects.equals(_searchFilter, newSearchFilter)) {
|
||||
_searchFilter = newSearchFilter;
|
||||
refreshEntryList();
|
||||
}
|
||||
}
|
||||
|
||||
sortShownEntries();
|
||||
checkPeriodUniformity();
|
||||
private void refreshEntryList() {
|
||||
replaceEntryList(new EntryList(
|
||||
_entryList.getEntries(),
|
||||
calculateShownEntries(_entryList.getEntries()),
|
||||
_entryList.getErrorCardInfo()
|
||||
));
|
||||
}
|
||||
|
||||
private void replaceEntryList(EntryList newEntryList) {
|
||||
DiffUtil.DiffResult diffRes = DiffUtil.calculateDiff(new DiffCallback(_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);
|
||||
_view.onListChange();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private List<VaultEntry> calculateShownEntries(List<VaultEntry> entries) {
|
||||
List<VaultEntry> res = new ArrayList<>();
|
||||
for (VaultEntry entry : entries) {
|
||||
if (!isEntryFiltered(entry)) {
|
||||
res.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
sortEntries(res, _sortCategory);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void sortEntries(List<VaultEntry> entries, SortCategory sortCategory) {
|
||||
if (sortCategory != null) {
|
||||
Comparator<VaultEntry> comparator = sortCategory.getComparator();
|
||||
if (comparator != null) {
|
||||
Collections.sort(entries, comparator);
|
||||
}
|
||||
}
|
||||
|
||||
Comparator<VaultEntry> favoriteComparator = new FavoriteComparator();
|
||||
Collections.sort(entries, favoriteComparator);
|
||||
}
|
||||
|
||||
private boolean isEntryDraggable(VaultEntry entry) {
|
||||
|
@ -412,18 +316,6 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
&& _selectedEntries.get(0) == entry;
|
||||
}
|
||||
|
||||
private void sortShownEntries() {
|
||||
if (_sortCategory != null) {
|
||||
Comparator<VaultEntry> comparator = _sortCategory.getComparator();
|
||||
if (comparator != null) {
|
||||
Collections.sort(_shownEntries, comparator);
|
||||
}
|
||||
}
|
||||
|
||||
Comparator<VaultEntry> favoriteComparator = new FavoriteComparator();
|
||||
Collections.sort(_shownEntries, favoriteComparator);
|
||||
}
|
||||
|
||||
public void setViewMode(ViewMode viewMode) {
|
||||
_viewMode = viewMode;
|
||||
}
|
||||
|
@ -439,7 +331,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
public Map<UUID, Long> getLastUsedTimestamps() { return _lastUsedTimestamps; }
|
||||
|
||||
public int getShownFavoritesCount() {
|
||||
return (int) _shownEntries.stream().filter(VaultEntry::isFavorite).count();
|
||||
return (int) _entryList.getShownEntries().stream().filter(VaultEntry::isFavorite).count();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -451,43 +343,48 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
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() || isPositionFooter(position) || isPositionErrorCard(position)) {
|
||||
if (!_groupFilter.isEmpty() || _entryList.isPositionFooter(position) || _entryList.isPositionErrorCard(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = translateEntryPosToIndex(position);
|
||||
_view.onEntryDrop(_shownEntries.get(index));
|
||||
int index = _entryList.translateEntryPosToIndex(position);
|
||||
_view.onEntryDrop(_entryList.getShownEntries().get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemMove(int firstPosition, int secondPosition) {
|
||||
// moving entries is not allowed when a filter is applied
|
||||
// footer cant be moved, nor can items be moved below it
|
||||
// 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()
|
||||
|| isPositionFooter(firstPosition) || isPositionFooter(secondPosition)
|
||||
|| isPositionErrorCard(firstPosition) || isPositionErrorCard(secondPosition)) {
|
||||
|| _entryList.isPositionFooter(firstPosition) || _entryList.isPositionFooter(secondPosition)
|
||||
|| _entryList.isPositionErrorCard(firstPosition) || _entryList.isPositionErrorCard(secondPosition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// notify the vault first
|
||||
int firstIndex = translateEntryPosToIndex(firstPosition);
|
||||
int secondIndex = translateEntryPosToIndex(secondPosition);
|
||||
_view.onEntryMove(_entries.get(firstIndex), _entries.get(secondIndex));
|
||||
// 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 our end
|
||||
CollectionUtils.move(_entries, firstIndex, secondIndex);
|
||||
CollectionUtils.move(_shownEntries, firstIndex, secondIndex);
|
||||
|
||||
notifyItemMoved(firstPosition, secondPosition);
|
||||
// Then update the visual end
|
||||
List<VaultEntry> newEntries = new ArrayList<>(_entryList.getEntries());
|
||||
CollectionUtils.move(newEntries, newEntries.indexOf(firstEntry), newEntries.indexOf(secondEntry));
|
||||
replaceEntryList(new EntryList(
|
||||
newEntries,
|
||||
calculateShownEntries(newEntries),
|
||||
_entryList.getErrorCardInfo()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (isPositionErrorCard(position)) {
|
||||
if (_entryList.isPositionErrorCard(position)) {
|
||||
return R.layout.card_error;
|
||||
}
|
||||
|
||||
if (isPositionFooter(position)) {
|
||||
if (_entryList.isPositionFooter(position)) {
|
||||
return R.layout.card_footer;
|
||||
}
|
||||
|
||||
|
@ -502,7 +399,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
View view = inflater.inflate(viewType, parent, false);
|
||||
|
||||
if (viewType == R.layout.card_error) {
|
||||
holder = new ErrorCardHolder(view, _errorCardInfo);
|
||||
holder = new ErrorCardHolder(view, Objects.requireNonNull(_entryList.getErrorCardInfo()));
|
||||
} else if (viewType == R.layout.card_footer) {
|
||||
holder = new FooterView(view);
|
||||
} else {
|
||||
|
@ -528,8 +425,8 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
|
||||
if (holder instanceof EntryHolder) {
|
||||
EntryHolder entryHolder = (EntryHolder) holder;
|
||||
int index = translateEntryPosToIndex(position);
|
||||
VaultEntry entry = _shownEntries.get(index);
|
||||
int index = _entryList.translateEntryPosToIndex(position);
|
||||
VaultEntry entry = _entryList.getShownEntries().get(index);
|
||||
|
||||
boolean hidden = _tapToReveal && entry != _focusedEntry;
|
||||
boolean paused = _pauseFocused && entry == _focusedEntry;
|
||||
|
@ -538,7 +435,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
boolean showAccountName = true;
|
||||
if (_onlyShowNecessaryAccountNames) {
|
||||
// Only show account name when there's multiple entries found with the same issuer.
|
||||
showAccountName = _entries.stream()
|
||||
showAccountName = _entryList.getEntries().stream()
|
||||
.filter(x -> x.getIssuer().equals(entry.getIssuer()))
|
||||
.count() > 1;
|
||||
}
|
||||
|
@ -616,8 +513,8 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
entryHolder.setFocusedAndAnimate(true);
|
||||
}
|
||||
|
||||
int index = translateEntryPosToIndex(position);
|
||||
boolean returnVal = _view.onLongEntryClick(_shownEntries.get(index));
|
||||
int index = _entryList.translateEntryPosToIndex(position);
|
||||
boolean returnVal = _view.onLongEntryClick(_entryList.getShownEntries().get(index));
|
||||
if (_selectedEntries.size() == 0 || isEntryDraggable(entry)) {
|
||||
_view.startDrag(entryHolder);
|
||||
}
|
||||
|
@ -663,15 +560,10 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
}
|
||||
|
||||
private void checkPeriodUniformity() {
|
||||
checkPeriodUniformity(false);
|
||||
}
|
||||
|
||||
private void checkPeriodUniformity(boolean force) {
|
||||
private void updatePeriodUniformity() {
|
||||
int mostFrequentPeriod = getMostFrequentPeriod();
|
||||
boolean uniform = isPeriodUniform();
|
||||
|
||||
if (!force && uniform == _isPeriodUniform && mostFrequentPeriod == _uniformPeriod) {
|
||||
if (uniform == _isPeriodUniform && mostFrequentPeriod == _uniformPeriod) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -689,7 +581,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
public int getMostFrequentPeriod() {
|
||||
List<TotpInfo> infos = new ArrayList<>();
|
||||
for (VaultEntry entry : _shownEntries) {
|
||||
for (VaultEntry entry : _entryList.getShownEntries()) {
|
||||
OtpInfo info = entry.getInfo();
|
||||
if (info instanceof TotpInfo) {
|
||||
infos.add((TotpInfo) info);
|
||||
|
@ -804,7 +696,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
public List<VaultEntry> selectAllEntries() {
|
||||
_selectedEntries.clear();
|
||||
|
||||
for (VaultEntry entry: _shownEntries) {
|
||||
for (VaultEntry entry: _entryList.getShownEntries()) {
|
||||
for (EntryHolder holder: _holders) {
|
||||
if (holder.getEntry() == entry) {
|
||||
holder.setFocused(true);
|
||||
|
@ -858,34 +750,23 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
@Override
|
||||
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++;
|
||||
}
|
||||
|
||||
return baseCount + getShownEntriesCount();
|
||||
return _entryList.getItemCount();
|
||||
}
|
||||
|
||||
public int getShownEntriesCount() {
|
||||
return _shownEntries.size();
|
||||
return _entryList.getShownEntries().size();
|
||||
}
|
||||
|
||||
public boolean isPositionFooter(int position) {
|
||||
return position == (getItemCount() - 1);
|
||||
return _entryList.isPositionFooter(position);
|
||||
}
|
||||
|
||||
public boolean isPositionErrorCard(int position) {
|
||||
return isErrorCardShown() && position == 0;
|
||||
return _entryList.isPositionErrorCard(position);
|
||||
}
|
||||
|
||||
public boolean isErrorCardShown() {
|
||||
return _errorCardInfo != null;
|
||||
}
|
||||
|
||||
private void updateFooter() {
|
||||
notifyItemChanged(getItemCount() - 1);
|
||||
return _entryList.isErrorCardShown();
|
||||
}
|
||||
|
||||
private class FooterView extends RecyclerView.ViewHolder {
|
||||
|
@ -912,6 +793,151 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
}
|
||||
|
||||
private static class EntryList {
|
||||
private final List<VaultEntry> _entries;
|
||||
private final List<VaultEntry> _shownEntries;
|
||||
private final ErrorCardInfo _errorCardInfo;
|
||||
|
||||
public EntryList() {
|
||||
this(new ArrayList<>(), new ArrayList<>(), null);
|
||||
}
|
||||
|
||||
public EntryList(
|
||||
@NonNull List<VaultEntry> entries,
|
||||
@NonNull List<VaultEntry> shownEntries,
|
||||
@Nullable ErrorCardInfo errorCardInfo
|
||||
) {
|
||||
_entries = entries;
|
||||
_shownEntries = shownEntries;
|
||||
_errorCardInfo = errorCardInfo;
|
||||
}
|
||||
|
||||
public List<VaultEntry> getEntries() {
|
||||
return _entries;
|
||||
}
|
||||
|
||||
public List<VaultEntry> getShownEntries() {
|
||||
return _shownEntries;
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
return baseCount + getShownEntries().size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ErrorCardInfo getErrorCardInfo() {
|
||||
return _errorCardInfo;
|
||||
}
|
||||
|
||||
public boolean isErrorCardShown() {
|
||||
return _errorCardInfo != null;
|
||||
}
|
||||
|
||||
public boolean isPositionErrorCard(int position) {
|
||||
return isErrorCardShown() && position == 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (isErrorCardShown()) {
|
||||
position -= 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (isErrorCardShown()) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DiffCallback extends DiffUtil.Callback {
|
||||
private final EntryList _old;
|
||||
private final EntryList _new;
|
||||
|
||||
public DiffCallback(EntryList oldList, EntryList newList) {
|
||||
_old = oldList;
|
||||
_new = newList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return _old.getItemCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return _new.getItemCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
if (_old.isPositionErrorCard(oldItemPosition) != _new.isPositionErrorCard(newItemPosition)
|
||||
|| _old.isPositionFooter(oldItemPosition) != _new.isPositionFooter(newItemPosition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((_old.isPositionFooter(oldItemPosition) && _new.isPositionFooter(newItemPosition))
|
||||
|| (_old.isPositionErrorCard(oldItemPosition) && _new.isPositionErrorCard(newItemPosition))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int oldEntryIndex = _old.translateEntryPosToIndex(oldItemPosition);
|
||||
int newEntryIndex = _new.translateEntryPosToIndex(newItemPosition);
|
||||
if (oldEntryIndex < 0 || newEntryIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _old.getShownEntries().get(oldEntryIndex).getUUID()
|
||||
.equals(_new.getShownEntries().get(newEntryIndex).getUUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
if (_old.isPositionFooter(oldItemPosition) && _new.isPositionFooter(newItemPosition)) {
|
||||
return _old.getShownEntries().size() == _new.getShownEntries().size();
|
||||
}
|
||||
|
||||
if (_old.isPositionErrorCard(oldItemPosition) && _new.isPositionErrorCard(newItemPosition)) {
|
||||
return Objects.equals(_old.getErrorCardInfo(), _new.getErrorCardInfo());
|
||||
}
|
||||
|
||||
int oldEntryIndex = _old.translateEntryPosToIndex(oldItemPosition);
|
||||
int newEntryIndex = _new.translateEntryPosToIndex(newItemPosition);
|
||||
return _old.getShownEntries().get(oldEntryIndex)
|
||||
.equals(_new.getShownEntries().get(newEntryIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onEntryClick(VaultEntry entry);
|
||||
boolean onLongEntryClick(VaultEntry entry);
|
||||
|
|
|
@ -50,6 +50,7 @@ import com.google.android.material.shape.CornerFamily;
|
|||
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -156,6 +157,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
_preloadSizeProvider.setView(view);
|
||||
}
|
||||
|
||||
public int getScrollPosition() {
|
||||
return ((LinearLayoutManager) _recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
}
|
||||
|
||||
public void scrollToPosition(int position) {
|
||||
_recyclerView.getLayoutManager().scrollToPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
_refresher.destroy();
|
||||
|
@ -167,14 +176,10 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
updateDividerDecoration();
|
||||
}
|
||||
|
||||
public void setGroupFilter(Set<UUID> groups, boolean animate) {
|
||||
public void setGroupFilter(Set<UUID> groups) {
|
||||
_adapter.setGroupFilter(groups);
|
||||
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());
|
||||
updateEmptyState();
|
||||
|
||||
if (animate) {
|
||||
runEntriesAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsLongPressDragEnabled(boolean enabled) {
|
||||
|
@ -207,10 +212,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
public void setSortCategory(SortCategory sortCategory, boolean apply) {
|
||||
_adapter.setSortCategory(sortCategory, apply);
|
||||
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());
|
||||
|
||||
if (apply) {
|
||||
runEntriesAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUsageCounts(Map<UUID, Integer> usageCounts) {
|
||||
|
@ -388,17 +389,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
_adapter.setErrorCardInfo(info);
|
||||
}
|
||||
|
||||
public void addEntry(VaultEntry entry) {
|
||||
addEntry(entry, false);
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void onEntryAdded(VaultEntry entry) {
|
||||
int position = _adapter.getEntryPosition(entry);
|
||||
if (position < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void addEntry(VaultEntry entry, boolean focusEntry) {
|
||||
int position = _adapter.addEntry(entry);
|
||||
updateEmptyState();
|
||||
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) _recyclerView.getLayoutManager();
|
||||
if (focusEntry && position >= 0) {
|
||||
if ((_recyclerView.canScrollVertically(1) && position > layoutManager.findLastCompletelyVisibleItemPosition())
|
||||
|| (_recyclerView.canScrollVertically(-1) && position < layoutManager.findFirstCompletelyVisibleItemPosition())) {
|
||||
boolean smoothScroll = !AnimationsHelper.Scale.TRANSITION.isZero(requireContext());
|
||||
|
@ -444,7 +442,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
tempHighlightEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void tempHighlightEntry(VaultEntry entry) {
|
||||
_adapter.setTempHighlightEntry(true);
|
||||
|
@ -453,27 +450,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
_adapter.focusEntry(entry, secondsToFocus);
|
||||
}
|
||||
|
||||
public void addEntries(Collection<VaultEntry> entries) {
|
||||
_adapter.addEntries(entries);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void removeEntry(VaultEntry entry) {
|
||||
_adapter.removeEntry(entry);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void removeEntry(UUID uuid) {
|
||||
_adapter.removeEntry(uuid);
|
||||
public void setEntries(Collection<VaultEntry> entries) {
|
||||
_adapter.setEntries(new ArrayList<>(entries));
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void clearEntries() {
|
||||
_adapter.clearEntries();
|
||||
}
|
||||
|
||||
public void replaceEntry(UUID uuid, VaultEntry newEntry) {
|
||||
_adapter.replaceEntry(uuid, newEntry);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void runEntriesAnimation() {
|
||||
|
@ -572,7 +556,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
// Only non-favorite entries have a bottom margin, except for the final favorite entry
|
||||
int totalFavorites = _adapter.getShownFavoritesCount();
|
||||
if (totalFavorites == 0
|
||||
|| (entryIndex < _adapter.getShownEntriesCount() && !_adapter.getEntryAtPos(adapterPosition).isFavorite())
|
||||
|| (entryIndex < _adapter.getShownEntriesCount() && !_adapter.getEntryAtPosition(adapterPosition).isFavorite())
|
||||
|| totalFavorites == entryIndex + 1) {
|
||||
outRect.bottom = _offset;
|
||||
}
|
||||
|
@ -665,7 +649,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
VaultEntry entry = _adapter.getEntryAtPos(position);
|
||||
VaultEntry entry = _adapter.getEntryAtPosition(position);
|
||||
if (!entry.hasIcon()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
|
|||
import androidx.core.util.AtomicFile;
|
||||
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.util.Cloner;
|
||||
import com.beemdevelopment.aegis.util.IOUtils;
|
||||
import com.google.zxing.WriterException;
|
||||
|
||||
|
@ -249,6 +250,13 @@ public class VaultRepository {
|
|||
return _vault.getEntries().replace(entry);
|
||||
}
|
||||
|
||||
public VaultEntry editEntry(VaultEntry entry, EntryEditor editor) {
|
||||
VaultEntry newEntry = Cloner.clone(entry);
|
||||
editor.edit(newEntry);
|
||||
replaceEntry(newEntry);
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves entry1 to the position of entry2.
|
||||
*/
|
||||
|
@ -344,4 +352,8 @@ public class VaultRepository {
|
|||
|
||||
return getCredentials().getSlots().findBackupPasswordSlots().size() > 0;
|
||||
}
|
||||
|
||||
public interface EntryEditor {
|
||||
void edit(VaultEntry entry);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue