Use stable IDs for the adapter

Fixes #346
This commit is contained in:
Jakob Nixdorf 2021-03-07 20:15:31 +01:00
parent ba5bb7705b
commit 283fb5630d
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
3 changed files with 319 additions and 239 deletions

View file

@ -79,6 +79,7 @@ public class Entry {
public static final int COLOR_RED = 1; public static final int COLOR_RED = 1;
private static final int EXPIRY_TIME = 8; private static final int EXPIRY_TIME = 8;
private int color = COLOR_DEFAULT; private int color = COLOR_DEFAULT;
private long listId = 0;
public Entry(){} public Entry(){}
@ -368,10 +369,6 @@ public class Entry {
return period; return period;
} }
public void setPeriod(int period) {
this.period = period;
}
public long getCounter() { public long getCounter() {
return counter; return counter;
} }
@ -384,10 +381,6 @@ public class Entry {
return digits; return digits;
} }
public void setDigits(int digits) {
this.digits = digits;
}
public List<String> getTags() { return tags; } public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; } public void setTags(List<String> tags) { this.tags = tags; }
@ -400,10 +393,6 @@ public class Entry {
return this.algorithm; return this.algorithm;
} }
public void setAlgorithm(TokenCalculator.HashAlgorithm algorithm) {
this.algorithm = algorithm;
}
public boolean hasNonDefaultPeriod() { public boolean hasNonDefaultPeriod() {
return this.period != TokenCalculator.TOTP_DEFAULT_PERIOD; return this.period != TokenCalculator.TOTP_DEFAULT_PERIOD;
} }
@ -444,6 +433,14 @@ public class Entry {
return currentOTP; return currentOTP;
} }
public long getListId() {
return listId;
}
public void setListId(long newId) {
listId = newId;
}
public boolean updateOTP() { public boolean updateOTP() {
if (type == OTPType.TOTP || type == OTPType.STEAM) { if (type == OTPType.TOTP || type == OTPType.STEAM) {
long time = System.currentTimeMillis() / 1000; long time = System.currentTimeMillis() / 1000;
@ -503,9 +500,14 @@ public class Entry {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o)
if (o == null || getClass() != o.getClass()) return false; return true;
if (o == null || getClass() != o.getClass())
return false;
Entry entry = (Entry) o; Entry entry = (Entry) o;
return type == entry.type && return type == entry.type &&
period == entry.period && period == entry.period &&
counter == entry.counter && counter == entry.counter &&
@ -541,9 +543,9 @@ public class Entry {
/** /**
* Returns the label with issuer prefix removed (if present) * Returns the label with issuer prefix removed (if present)
* @param issuer * @param issuer - Name of the issuer to remove from the label
* @param label * @param label - Full label from which the issuer should be removed
* @return * @return - label with the issuer removed
*/ */
private String getStrippedLabel(String issuer, String label) { private String getStrippedLabel(String issuer, String label) {
if (issuer == null || issuer.isEmpty() || !label.startsWith(issuer + ":")) { if (issuer == null || issuer.isEmpty() || !label.startsWith(issuer + ":")) {

View file

@ -0,0 +1,205 @@
package org.shadowice.flocke.andotp.Database;
import org.shadowice.flocke.andotp.Utilities.Constants;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
public class EntryList {
private final ArrayList<Entry> entries;
private final AtomicLong currentId = new AtomicLong();
public EntryList() {
entries = new ArrayList<>();
}
public boolean addEntry(Entry newEntry) {
return addEntry(newEntry, false);
}
public boolean addEntry(Entry newEntry, boolean update) {
if (! entries.contains(newEntry)) {
long newId = currentId.incrementAndGet();
newEntry.setListId(newId);
entries.add(newEntry);
return true;
} else {
if (update) {
int oldIdx = entries.indexOf(newEntry);
Entry oldEntry = entries.get(oldIdx);
newEntry.setListId(oldEntry.getListId());
entries.set(oldIdx, newEntry);
}
}
return false;
}
public void updateEntries(ArrayList<Entry> newEntries, boolean update) {
// Remove all items not in the new list
entries.retainAll(newEntries);
// Add new and update existing entries
for (Entry e : newEntries) {
addEntry(e, update);
}
}
public Entry getEntry(int pos) {
return entries.get(pos);
}
public void swapEntries(int fromPosition, int toPosition) {
Collections.swap(entries, fromPosition, toPosition);
}
public void removeEntry(int pos) {
entries.remove(pos);
}
public int indexOf(Entry e) {
return entries.indexOf(e);
}
public boolean isEqual(ArrayList<Entry> otherEntries) {
return entries.equals(otherEntries);
}
public ArrayList<Entry> getEntries() {
return new ArrayList<>(entries);
}
public ArrayList<Entry> getEntriesSorted(Constants.SortMode sortMode) {
return sortEntries(entries, sortMode);
}
public static ArrayList<Entry> sortEntries(ArrayList<Entry> unsortedEntries, Constants.SortMode sortMode) {
ArrayList<Entry> sorted = new ArrayList<>(unsortedEntries);
if (sortMode == Constants.SortMode.ISSUER) {
Collections.sort(sorted, new IssuerComparator());
} else if (sortMode == Constants.SortMode.LABEL) {
Collections.sort(sorted, new LabelComparator());
} else if (sortMode == Constants.SortMode.LAST_USED) {
Collections.sort(sorted, new LastUsedComparator());
} else if (sortMode == Constants.SortMode.MOST_USED) {
Collections.sort(sorted, new MostUsedComparator());
}
return sorted;
}
public ArrayList<String> getAllTags() {
HashSet<String> tags = new HashSet<>();
for(Entry entry : entries) {
tags.addAll(entry.getTags());
}
return new ArrayList<>(tags);
}
public ArrayList<Entry> getFilteredEntries(CharSequence constraint, List<Constants.SearchIncludes> filterValues, Constants.SortMode sortMode) {
ArrayList<Entry> filtered = new ArrayList<>();
if (constraint != null && constraint.length() != 0){
for (int i = 0; i < entries.size(); i++) {
if (filterValues.contains(Constants.SearchIncludes.LABEL) && entries.get(i).getLabel().toLowerCase().contains(constraint.toString().toLowerCase())) {
filtered.add(entries.get(i));
} else if (filterValues.contains(Constants.SearchIncludes.ISSUER) && entries.get(i).getIssuer().toLowerCase().contains(constraint.toString().toLowerCase())) {
filtered.add(entries.get(i));
} else if (filterValues.contains(Constants.SearchIncludes.TAGS)) {
List<String> tags = entries.get(i).getTags();
for (int j = 0; j < tags.size(); j++) {
if (tags.get(j).toLowerCase().contains(constraint.toString().toLowerCase())) {
filtered.add(entries.get(i));
break;
}
}
}
}
} else {
filtered = entries;
}
return sortEntries(filtered, sortMode);
}
public ArrayList<Entry> getEntriesFilteredByTags(List<String> tags, boolean noTags, Constants.TagFunctionality tagFunctionality, Constants.SortMode sortMode) {
ArrayList<Entry> matchingEntries = new ArrayList<>();
for(Entry e : entries) {
// Entries with no tags will always be shown
boolean foundMatchingTag = e.getTags().isEmpty() && noTags;
if(tagFunctionality == Constants.TagFunctionality.AND) {
if(e.getTags().containsAll(tags)) {
foundMatchingTag = true;
}
} else {
for (String tag : tags) {
if (e.getTags().contains(tag)) {
foundMatchingTag = true;
break;
}
}
}
if(foundMatchingTag) {
matchingEntries.add(e);
}
}
return sortEntries(matchingEntries, sortMode);
}
public static class IssuerComparator implements Comparator<Entry> {
Collator collator;
IssuerComparator(){
collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
}
@Override
public int compare(Entry o1, Entry o2) {
return collator.compare(o1.getIssuer(), o2.getIssuer());
}
}
public static class LabelComparator implements Comparator<Entry> {
Collator collator;
LabelComparator(){
collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
}
@Override
public int compare(Entry o1, Entry o2) {
return collator.compare(o1.getLabel(), o2.getLabel());
}
}
public static class LastUsedComparator implements Comparator<Entry> {
@Override
public int compare(Entry o1, Entry o2) {
return Long.compare(o2.getLastUsed(), o1.getLastUsed());
}
}
public static class MostUsedComparator implements Comparator<Entry> {
@Override
public int compare(Entry o1, Entry o2) {
return Long.compare(o2.getUsedFrequency(), o1.getUsedFrequency());
}
}
}

View file

@ -22,6 +22,7 @@
package org.shadowice.flocke.andotp.View; package org.shadowice.flocke.andotp.View;
import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -38,10 +39,8 @@ import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Filter; import android.widget.Filter;
@ -57,6 +56,7 @@ import com.journeyapps.barcodescanner.BarcodeEncoder;
import org.shadowice.flocke.andotp.Activities.MainActivity; import org.shadowice.flocke.andotp.Activities.MainActivity;
import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.Database.EntryList;
import org.shadowice.flocke.andotp.Dialogs.ManualEntryDialog; import org.shadowice.flocke.andotp.Dialogs.ManualEntryDialog;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.BackupHelper; import org.shadowice.flocke.andotp.Utilities.BackupHelper;
@ -68,12 +68,8 @@ import org.shadowice.flocke.andotp.Utilities.Settings;
import org.shadowice.flocke.andotp.Utilities.Tools; import org.shadowice.flocke.andotp.Utilities.Tools;
import org.shadowice.flocke.andotp.View.ItemTouchHelper.ItemTouchHelperAdapter; import org.shadowice.flocke.andotp.View.ItemTouchHelper.ItemTouchHelperAdapter;
import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -83,10 +79,10 @@ import static org.shadowice.flocke.andotp.Utilities.Constants.SortMode;
public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder> public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
implements ItemTouchHelperAdapter, Filterable { implements ItemTouchHelperAdapter, Filterable {
private Context context; private final Context context;
private Handler taskHandler; private final Handler taskHandler;
private EntryFilter filter; private EntryFilter filter;
private ArrayList<Entry> entries; private final EntryList entries;
private ArrayList<Entry> displayedEntries; private ArrayList<Entry> displayedEntries;
private Callback callback; private Callback callback;
private List<String> tagsFilter = new ArrayList<>(); private List<String> tagsFilter = new ArrayList<>();
@ -94,15 +90,17 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
private SecretKey encryptionKey = null; private SecretKey encryptionKey = null;
private SortMode sortMode = SortMode.UNSORTED; private SortMode sortMode = SortMode.UNSORTED;
private TagsAdapter tagsFilterAdapter; private final TagsAdapter tagsFilterAdapter;
private Settings settings; private final Settings settings;
public EntriesCardAdapter(Context context, TagsAdapter tagsFilterAdapter) { public EntriesCardAdapter(Context context, TagsAdapter tagsFilterAdapter) {
this.context = context; this.context = context;
this.tagsFilterAdapter = tagsFilterAdapter; this.tagsFilterAdapter = tagsFilterAdapter;
this.settings = new Settings(context); this.settings = new Settings(context);
this.taskHandler = new Handler(); this.taskHandler = new Handler();
this.entries = new ArrayList<>(); this.entries = new EntryList();
setHasStableIds(true);
} }
public void setEncryptionKey(SecretKey key) { public void setEncryptionKey(SecretKey key) {
@ -118,8 +116,13 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
return displayedEntries.size(); return displayedEntries.size();
} }
@Override
public long getItemId(int position) {
return displayedEntries.get(position).getListId();
}
public ArrayList<Entry> getEntries() { public ArrayList<Entry> getEntries() {
return entries; return entries.getEntries();
} }
public void saveAndRefresh(boolean auto_backup) { public void saveAndRefresh(boolean auto_backup) {
@ -129,8 +132,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
} }
public void addEntry(Entry e) { public void addEntry(Entry e) {
if (! entries.contains(e)) { if (entries.addEntry(e)) {
entries.add(e);
saveAndRefresh(settings.getAutoBackupEncryptedPasswordsEnabled()); saveAndRefresh(settings.getAutoBackupEncryptedPasswordsEnabled());
} else { } else {
Toast.makeText(context, R.string.toast_entry_exists, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.toast_entry_exists, Toast.LENGTH_LONG).show();
@ -142,7 +144,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
} }
private void entriesChanged() { private void entriesChanged() {
displayedEntries = sortEntries(entries); displayedEntries = entries.getEntriesSorted(sortMode);
filterByTags(tagsFilter); filterByTags(tagsFilter);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -170,7 +172,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
} }
public void saveEntries(boolean auto_backup) { public void saveEntries(boolean auto_backup) {
DatabaseHelper.saveDatabase(context, entries, encryptionKey); DatabaseHelper.saveDatabase(context, entries.getEntries(), encryptionKey);
if(auto_backup) { if(auto_backup) {
Constants.BackupType backupType = BackupHelper.autoBackupType(context); Constants.BackupType backupType = BackupHelper.autoBackupType(context);
@ -196,44 +198,24 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
public void loadEntries() { public void loadEntries() {
if (encryptionKey != null) { if (encryptionKey != null) {
entries = DatabaseHelper.loadDatabase(context, encryptionKey); ArrayList<Entry> newEntries = DatabaseHelper.loadDatabase(context, encryptionKey);
entries.updateEntries(newEntries, true);
entriesChanged(); entriesChanged();
} }
} }
public void filterByTags(List<String> tags) { public void filterByTags(List<String> tags) {
displayedEntries = entries.getEntriesFilteredByTags(tags, settings.getNoTagsToggle(), settings.getTagFunctionality(), sortMode);
tagsFilter = tags; tagsFilter = tags;
List<Entry> matchingEntries = new ArrayList<>();
for(Entry e : entries) {
//Entries with no tags will always be shown
Boolean foundMatchingTag = e.getTags().isEmpty() && settings.getNoTagsToggle();
if(settings.getTagFunctionality() == Constants.TagFunctionality.AND) {
if(e.getTags().containsAll(tags)) {
foundMatchingTag = true;
}
} else {
for (String tag : tags) {
if (e.getTags().contains(tag)) {
foundMatchingTag = true;
}
}
}
if(foundMatchingTag) {
matchingEntries.add(e);
}
}
displayedEntries = sortEntries(matchingEntries);
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void updateTimeBasedTokens() { public void updateTimeBasedTokens() {
boolean change = false; boolean change = false;
for (Entry e : entries) { for (Entry e : entries.getEntries()) {
if (e.isTimeBased()) { if (e.isTimeBased()) {
boolean cardVisible = !settings.getTapToReveal() || e.isVisible(); boolean cardVisible = !settings.getTapToReveal() || e.isVisible();
@ -348,7 +330,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
@Override @Override
public void onCounterClicked(int position) { public void onCounterClicked(int position) {
updateEntry(displayedEntries.get(position), entries.get(getRealIndex(position)), position); updateEntry(displayedEntries.get(position), entries.getEntry(getRealIndex(position)), position);
} }
@Override @Override
@ -375,16 +357,11 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
if (entry.isVisible()) { if (entry.isVisible()) {
hideEntry(entry); hideEntry(entry);
} else { } else {
entries.get(realIndex).setHideTask(new Runnable() { entries.getEntry(realIndex).setHideTask(() -> hideEntry(entry));
@Override taskHandler.postDelayed(entries.getEntry(realIndex).getHideTask(), settings.getTapToRevealTimeout() * 1000);
public void run() {
hideEntry(entry);
}
});
taskHandler.postDelayed(entries.get(realIndex).getHideTask(), settings.getTapToRevealTimeout() * 1000);
if (entry.isCounterBased()) { if (entry.isCounterBased()) {
updateEntry(entry, entries.get(realIndex), position); updateEntry(entry, entries.getEntry(realIndex), position);
} }
entry.setVisible(true); entry.setVisible(true);
notifyItemChanged(position); notifyItemChanged(position);
@ -409,9 +386,9 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
int realIndex = entries.indexOf(entry); int realIndex = entries.indexOf(entry);
if (realIndex >= 0) { if (realIndex >= 0) {
entries.get(realIndex).setVisible(false); entries.getEntry(realIndex).setVisible(false);
taskHandler.removeCallbacks(entries.get(realIndex).getHideTask()); taskHandler.removeCallbacks(entries.getEntry(realIndex).getHideTask());
entries.get(realIndex).setHideTask(null); entries.getEntry(realIndex).setHideTask(null);
} }
boolean updateNeeded = updateLastUsedAndFrequency(pos, realIndex); boolean updateNeeded = updateLastUsedAndFrequency(pos, realIndex);
@ -442,25 +419,19 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
AlertDialog dialog = builder.setTitle(R.string.dialog_title_counter) AlertDialog dialog = builder.setTitle(R.string.dialog_title_counter)
.setView(container) .setView(container)
.setPositiveButton(R.string.button_save, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.button_save, (dialogInterface, i) -> {
@Override int realIndex = getRealIndex(pos);
public void onClick(DialogInterface dialogInterface, int i) { long newCounter = Long.parseLong(input.getEditableText().toString());
int realIndex = getRealIndex(pos);
long newCounter = Long.parseLong(input.getEditableText().toString());
displayedEntries.get(pos).setCounter(newCounter); displayedEntries.get(pos).setCounter(newCounter);
notifyItemChanged(pos); notifyItemChanged(pos);
Entry e = entries.get(realIndex); Entry e = entries.getEntry(realIndex);
e.setCounter(newCounter); e.setCounter(newCounter);
saveEntries(settings.getAutoBackupEncryptedFullEnabled()); saveEntries(settings.getAutoBackupEncryptedFullEnabled());
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {}
}) })
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {})
.create(); .create();
addCounterValidationWatcher(input, dialog); addCounterValidationWatcher(input, dialog);
dialog.show(); dialog.show();
@ -497,7 +468,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
private boolean updateLastUsedAndFrequency(int position, int realIndex) { private boolean updateLastUsedAndFrequency(int position, int realIndex) {
long timeStamp = System.currentTimeMillis(); long timeStamp = System.currentTimeMillis();
long entryUsedFrequency = entries.get(realIndex).getUsedFrequency(); long entryUsedFrequency = entries.getEntry(realIndex).getUsedFrequency();
if (position >= 0) { if (position >= 0) {
long displayEntryUsedFrequency = displayedEntries.get(position).getUsedFrequency(); long displayEntryUsedFrequency = displayedEntries.get(position).getUsedFrequency();
@ -505,16 +476,16 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
displayedEntries.get(position).setUsedFrequency(displayEntryUsedFrequency + 1); displayedEntries.get(position).setUsedFrequency(displayEntryUsedFrequency + 1);
} }
entries.get(realIndex).setLastUsed(timeStamp); entries.getEntry(realIndex).setLastUsed(timeStamp);
entries.get(realIndex).setUsedFrequency(entryUsedFrequency + 1); entries.getEntry(realIndex).setUsedFrequency(entryUsedFrequency + 1);
saveEntries(false); saveEntries(false);
if (sortMode == SortMode.LAST_USED) { if (sortMode == SortMode.LAST_USED) {
displayedEntries = sortEntries(displayedEntries); displayedEntries = EntryList.sortEntries(displayedEntries, sortMode);
notifyDataSetChanged(); notifyDataSetChanged();
return false; return false;
} else if (sortMode == SortMode.MOST_USED) { } else if (sortMode == SortMode.MOST_USED) {
displayedEntries = sortEntries(displayedEntries); displayedEntries = EntryList.sortEntries(displayedEntries, sortMode);
notifyDataSetChanged(); notifyDataSetChanged();
return false; return false;
} }
@ -524,10 +495,10 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
@Override @Override
public boolean onItemMove(int fromPosition, int toPosition) { public boolean onItemMove(int fromPosition, int toPosition) {
if (sortMode == SortMode.UNSORTED && displayedEntries.equals(entries)) { if (sortMode == SortMode.UNSORTED && entries.isEqual(displayedEntries)) {
Collections.swap(entries, fromPosition, toPosition); entries.swapEntries(fromPosition, toPosition);
displayedEntries = new ArrayList<>(entries); displayedEntries = entries.getEntries();
notifyItemMoved(fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition);
saveEntries(false); saveEntries(false);
@ -543,7 +514,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
int marginMedium = context.getResources().getDimensionPixelSize(R.dimen.activity_margin_medium); int marginMedium = context.getResources().getDimensionPixelSize(R.dimen.activity_margin_medium);
int realIndex = getRealIndex(pos); int realIndex = getRealIndex(pos);
final ThumbnailSelectionAdapter thumbnailAdapter = new ThumbnailSelectionAdapter(context, entries.get(realIndex).getIssuer(), entries.get(realIndex).getLabel()); final ThumbnailSelectionAdapter thumbnailAdapter = new ThumbnailSelectionAdapter(context, entries.getEntry(realIndex).getIssuer(), entries.getEntry(realIndex).getLabel());
final EditText input = new EditText(context); final EditText input = new EditText(context);
input.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); input.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
@ -590,36 +561,31 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
final AlertDialog alert = builder.setTitle(R.string.menu_popup_change_image) final AlertDialog alert = builder.setTitle(R.string.menu_popup_change_image)
.setView(container) .setView(container)
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { .setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {})
@Override
public void onClick(DialogInterface dialogInterface, int i) {}
})
.create(); .create();
grid.setOnItemClickListener(new AdapterView.OnItemClickListener() { grid.setOnItemClickListener((parent, view, position, id) -> {
@Override int realIndex1 = getRealIndex(pos);
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { EntryThumbnail.EntryThumbnails thumbnail = EntryThumbnail.EntryThumbnails.Default;
int realIndex = getRealIndex(pos); try {
EntryThumbnail.EntryThumbnails thumbnail = EntryThumbnail.EntryThumbnails.Default; int realPos = thumbnailAdapter.getRealIndex(position);
try { thumbnail = EntryThumbnail.EntryThumbnails.values()[realPos];
int realPos = thumbnailAdapter.getRealIndex(position); } catch (Exception e) {
thumbnail = EntryThumbnail.EntryThumbnails.values()[realPos]; e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Entry e = entries.get(realIndex);
e.setThumbnail(thumbnail);
saveEntries(settings.getAutoBackupEncryptedFullEnabled());
notifyItemChanged(pos);
alert.cancel();
} }
Entry e = entries.getEntry(realIndex1);
e.setThumbnail(thumbnail);
saveEntries(settings.getAutoBackupEncryptedFullEnabled());
notifyItemChanged(pos);
alert.cancel();
}); });
alert.show(); alert.show();
} }
@SuppressLint("StringFormatInvalid")
public void removeItem(final int pos) { public void removeItem(final int pos) {
AlertDialog.Builder builder = new AlertDialog.Builder(context); AlertDialog.Builder builder = new AlertDialog.Builder(context);
@ -628,22 +594,16 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
builder.setTitle(R.string.dialog_title_remove) builder.setTitle(R.string.dialog_title_remove)
.setMessage(message) .setMessage(message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.yes, (dialogInterface, i) -> {
@Override int realIndex = getRealIndex(pos);
public void onClick(DialogInterface dialogInterface, int i) {
int realIndex = getRealIndex(pos);
displayedEntries.remove(pos); displayedEntries.remove(pos);
notifyItemRemoved(pos); notifyItemRemoved(pos);
entries.remove(realIndex); entries.removeEntry(realIndex);
saveEntries(settings.getAutoBackupEncryptedFullEnabled()); saveEntries(settings.getAutoBackupEncryptedFullEnabled());
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {}
}) })
.setNegativeButton(android.R.string.no, (dialogInterface, i) -> {})
.show(); .show();
} }
@ -682,26 +642,23 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
MenuInflater inflate = popup.getMenuInflater(); MenuInflater inflate = popup.getMenuInflater();
inflate.inflate(R.menu.menu_popup, popup.getMenu()); inflate.inflate(R.menu.menu_popup, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popup.setOnMenuItemClickListener(item -> {
@Override int id = item.getItemId();
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu_popup_edit) { if (id == R.id.menu_popup_edit) {
ManualEntryDialog.show((MainActivity) context, settings, EntriesCardAdapter.this, entries.get(getRealIndex(pos))); ManualEntryDialog.show((MainActivity) context, settings, EntriesCardAdapter.this, entries.getEntry(getRealIndex(pos)));
return true; return true;
} else if(id == R.id.menu_popup_changeImage) { } else if(id == R.id.menu_popup_changeImage) {
changeThumbnail(pos); changeThumbnail(pos);
return true; return true;
} else if (id == R.id.menu_popup_remove) { } else if (id == R.id.menu_popup_remove) {
removeItem(pos); removeItem(pos);
return true; return true;
} else if (id == R.id.menu_popup_show_qr_code) { } else if (id == R.id.menu_popup_show_qr_code) {
showQRCode(pos); showQRCode(pos);
return true; return true;
} else { } else {
return false; return false;
}
} }
}); });
popup.show(); popup.show();
@ -716,22 +673,6 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
return this.sortMode; return this.sortMode;
} }
private ArrayList<Entry> sortEntries(List<Entry> unsorted) {
ArrayList<Entry> sorted = new ArrayList<>(unsorted);
if (sortMode == SortMode.ISSUER) {
Collections.sort(sorted, new IssuerComparator());
} else if (sortMode == SortMode.LABEL) {
Collections.sort(sorted, new LabelComparator());
} else if (sortMode == SortMode.LAST_USED) {
Collections.sort(sorted, new LastUsedComparator());
} else if (sortMode == SortMode.MOST_USED) {
Collections.sort(sorted, new MostUsedComparator());
}
return sorted;
}
public void setCallback(Callback cb) { public void setCallback(Callback cb) {
this.callback = cb; this.callback = cb;
} }
@ -749,44 +690,17 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
} }
public List<String> getTags() { public List<String> getTags() {
HashSet<String> tags = new HashSet<String>(); return entries.getAllTags();
for(Entry entry : entries) {
tags.addAll(entry.getTags());
}
return new ArrayList<String>(tags);
} }
public class EntryFilter extends Filter { public class EntryFilter extends Filter {
private List<Constants.SearchIncludes> filterValues = settings.getSearchValues(); private final List<Constants.SearchIncludes> filterValues = settings.getSearchValues();
@Override @Override
protected FilterResults performFiltering(CharSequence constraint) { protected FilterResults performFiltering(CharSequence constraint) {
ArrayList<Entry> filtered = entries.getFilteredEntries(constraint, filterValues, sortMode);
final FilterResults filterResults = new FilterResults(); final FilterResults filterResults = new FilterResults();
ArrayList<Entry> filtered = new ArrayList<>();
if (constraint != null && constraint.length() != 0){
for (int i = 0; i < entries.size(); i++) {
if (filterValues.contains(Constants.SearchIncludes.LABEL) && entries.get(i).getLabel().toLowerCase().contains(constraint.toString().toLowerCase())) {
filtered.add(entries.get(i));
} else if (filterValues.contains(Constants.SearchIncludes.ISSUER) && entries.get(i).getIssuer().toLowerCase().contains(constraint.toString().toLowerCase())) {
filtered.add(entries.get(i));
} else if (filterValues.contains(Constants.SearchIncludes.TAGS)) {
List<String> tags = entries.get(i).getTags();
for (int j = 0; j < tags.size(); j++) {
if (tags.get(j).toLowerCase().contains(constraint.toString().toLowerCase())) {
filtered.add(entries.get(i));
break;
}
}
}
}
} else {
filtered = entries;
}
filterResults.count = filtered.size(); filterResults.count = filtered.size();
filterResults.values = filtered; filterResults.values = filtered;
@ -794,54 +708,13 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntryViewHolder>
} }
@Override @Override
protected void publishResults(CharSequence constraint, FilterResults results) { @SuppressWarnings("unchecked")
displayedEntries = sortEntries((ArrayList<Entry>) results.values); protected void publishResults(CharSequence constraint, @NonNull FilterResults results) {
displayedEntries = (ArrayList<Entry>) results.values;
notifyDataSetChanged(); notifyDataSetChanged();
} }
} }
public class IssuerComparator implements Comparator<Entry> {
Collator collator;
IssuerComparator(){
collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
}
@Override
public int compare(Entry o1, Entry o2) {
return collator.compare(o1.getIssuer(), o2.getIssuer());
}
}
public class LabelComparator implements Comparator<Entry> {
Collator collator;
LabelComparator(){
collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
}
@Override
public int compare(Entry o1, Entry o2) {
return collator.compare(o1.getLabel(), o2.getLabel());
}
}
public class LastUsedComparator implements Comparator<Entry> {
@Override
public int compare(Entry o1, Entry o2) {
return Long.compare(o2.getLastUsed(), o1.getLastUsed());
}
}
public class MostUsedComparator implements Comparator<Entry> {
@Override
public int compare(Entry o1, Entry o2) {
return Long.compare(o2.getUsedFrequency(), o1.getUsedFrequency());
}
}
public interface Callback { public interface Callback {
void onMoveEventStart(); void onMoveEventStart();
void onMoveEventStop(); void onMoveEventStop();