mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-06-24 18:10:58 +00:00
Merge pull request #1505 from alexbakker/diffutil
Use DiffUtil for the RecyclerView of the entry list
This commit is contained in:
commit
bc29242f55
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 =
|
||||
|
@ -256,13 +256,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,7 +317,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
if (!isChecked) {
|
||||
group1.setChecked(false);
|
||||
_groupFilter = groupFilter;
|
||||
_entryListView.setGroupFilter(groupFilter, false);
|
||||
_entryListView.setGroupFilter(groupFilter);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -329,7 +329,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
}
|
||||
|
||||
_groupFilter = groupFilter;
|
||||
_entryListView.setGroupFilter(groupFilter, false);
|
||||
_entryListView.setGroupFilter(groupFilter);
|
||||
});
|
||||
|
||||
chipGroup.addView(chip);
|
||||
|
@ -574,31 +574,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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -696,14 +685,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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -926,15 +912,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;
|
||||
|
@ -1064,7 +1041,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();
|
||||
}
|
||||
|
@ -1292,6 +1269,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;
|
||||
|
@ -1388,12 +1372,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);
|
||||
|
@ -1411,8 +1396,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;
|
||||
|
@ -78,14 +79,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<>();
|
||||
|
@ -150,173 +149,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;
|
||||
}
|
||||
|
||||
replaceEntryList(new EntryList(
|
||||
_entryList.getEntries(),
|
||||
_entryList.getShownEntries(),
|
||||
info
|
||||
));
|
||||
}
|
||||
|
||||
public VaultEntry getEntryAtPos(int position) {
|
||||
return _shownEntries.get(translateEntryPosToIndex(position));
|
||||
public VaultEntry getEntryAtPosition(int position) {
|
||||
return _entryList.getShownEntries().get(_entryList.translateEntryPosToIndex(position));
|
||||
}
|
||||
|
||||
public int addEntry(VaultEntry entry) {
|
||||
_entries.add(entry);
|
||||
if (isEntryFiltered(entry)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (position < 0) {
|
||||
_shownEntries.add(entry);
|
||||
|
||||
position = translateEntryIndexToPos(getShownEntriesCount() - 1);
|
||||
if (position == 0) {
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
}
|
||||
|
||||
_view.onListChange();
|
||||
checkPeriodUniformity();
|
||||
updateFooter();
|
||||
return position;
|
||||
public int getEntryPosition(VaultEntry entry) {
|
||||
return _entryList.translateEntryIndexToPos(_entryList.getShownEntries().indexOf(entry));
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -353,7 +224,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();
|
||||
|
@ -368,8 +239,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
|
||||
_groupFilter = groups;
|
||||
updateShownEntries();
|
||||
checkPeriodUniformity();
|
||||
refreshEntryList();
|
||||
}
|
||||
|
||||
public void setSortCategory(SortCategory category, boolean apply) {
|
||||
|
@ -379,7 +249,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
_sortCategory = category;
|
||||
if (apply) {
|
||||
updateShownEntries();
|
||||
refreshEntryList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,25 +258,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;
|
||||
|
||||
if (!Objects.equals(_searchFilter, newSearchFilter)) {
|
||||
_searchFilter = newSearchFilter;
|
||||
refreshEntryList();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShownEntries() {
|
||||
// clear the list of shown entries first
|
||||
_shownEntries.clear();
|
||||
private void refreshEntryList() {
|
||||
replaceEntryList(new EntryList(
|
||||
_entryList.getEntries(),
|
||||
calculateShownEntries(_entryList.getEntries()),
|
||||
_entryList.getErrorCardInfo()
|
||||
));
|
||||
}
|
||||
|
||||
// add entries back that are not filtered out
|
||||
for (VaultEntry entry : _entries) {
|
||||
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();
|
||||
}
|
||||
|
||||
private List<VaultEntry> calculateShownEntries(List<VaultEntry> entries) {
|
||||
List<VaultEntry> res = new ArrayList<>();
|
||||
for (VaultEntry entry : entries) {
|
||||
if (!isEntryFiltered(entry)) {
|
||||
_shownEntries.add(entry);
|
||||
res.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
sortShownEntries();
|
||||
checkPeriodUniformity();
|
||||
_view.onListChange();
|
||||
notifyDataSetChanged();
|
||||
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) {
|
||||
|
@ -417,18 +321,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;
|
||||
}
|
||||
|
@ -444,7 +336,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
|
||||
|
@ -456,43 +348,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;
|
||||
}
|
||||
|
||||
|
@ -507,7 +404,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 {
|
||||
|
@ -533,8 +430,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;
|
||||
|
@ -543,7 +440,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;
|
||||
}
|
||||
|
@ -621,8 +518,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);
|
||||
}
|
||||
|
@ -668,15 +565,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;
|
||||
}
|
||||
|
||||
|
@ -694,7 +586,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);
|
||||
|
@ -809,7 +701,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);
|
||||
|
@ -863,34 +755,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 {
|
||||
|
@ -917,6 +798,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) {
|
||||
|
@ -392,61 +393,57 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
_adapter.setErrorCardInfo(info);
|
||||
}
|
||||
|
||||
public void addEntry(VaultEntry entry) {
|
||||
addEntry(entry, false);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void addEntry(VaultEntry entry, boolean focusEntry) {
|
||||
int position = _adapter.addEntry(entry);
|
||||
updateEmptyState();
|
||||
public void onEntryAdded(VaultEntry entry) {
|
||||
int position = _adapter.getEntryPosition(entry);
|
||||
if (position < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
|
||||
private void handleScroll() {
|
||||
_recyclerView.removeOnScrollListener(this);
|
||||
_recyclerView.setOnTouchListener(null);
|
||||
tempHighlightEntry(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
if (smoothScroll && newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
handleScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
if (!smoothScroll) {
|
||||
handleScroll();
|
||||
}
|
||||
}
|
||||
};
|
||||
_recyclerView.addOnScrollListener(scrollListener);
|
||||
_recyclerView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
_recyclerView.removeOnScrollListener(scrollListener);
|
||||
_recyclerView.stopScroll();
|
||||
_recyclerView.setOnTouchListener(null);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
// We can't easily control the speed of the smooth scroll animation, but we
|
||||
// can at least disable it if animations are disabled
|
||||
if (smoothScroll) {
|
||||
_recyclerView.smoothScrollToPosition(position);
|
||||
} else {
|
||||
_recyclerView.scrollToPosition(position);
|
||||
if ((_recyclerView.canScrollVertically(1) && position > layoutManager.findLastCompletelyVisibleItemPosition())
|
||||
|| (_recyclerView.canScrollVertically(-1) && position < layoutManager.findFirstCompletelyVisibleItemPosition())) {
|
||||
boolean smoothScroll = !AnimationsHelper.Scale.TRANSITION.isZero(requireContext());
|
||||
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
|
||||
private void handleScroll() {
|
||||
_recyclerView.removeOnScrollListener(this);
|
||||
_recyclerView.setOnTouchListener(null);
|
||||
tempHighlightEntry(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
if (smoothScroll && newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
handleScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
if (!smoothScroll) {
|
||||
handleScroll();
|
||||
}
|
||||
}
|
||||
};
|
||||
_recyclerView.addOnScrollListener(scrollListener);
|
||||
_recyclerView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
_recyclerView.removeOnScrollListener(scrollListener);
|
||||
_recyclerView.stopScroll();
|
||||
_recyclerView.setOnTouchListener(null);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
// We can't easily control the speed of the smooth scroll animation, but we
|
||||
// can at least disable it if animations are disabled
|
||||
if (smoothScroll) {
|
||||
_recyclerView.smoothScrollToPosition(position);
|
||||
} else {
|
||||
tempHighlightEntry(entry);
|
||||
_recyclerView.scrollToPosition(position);
|
||||
}
|
||||
} else {
|
||||
tempHighlightEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -457,27 +454,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() {
|
||||
|
@ -576,7 +560,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;
|
||||
}
|
||||
|
@ -669,7 +653,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
Add a link
Reference in a new issue