Merge pull request #279 from michaelschattgen/feature/multi-select

Add ability to select multiple entries
This commit is contained in:
Alexander Bakker 2020-01-05 11:42:36 +01:00 committed by GitHub
commit 97d824d779
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 107 additions and 23 deletions

View file

@ -57,6 +57,24 @@ public class Dialogs {
.create()); .create());
} }
public static void showDeleteEntriesDialog(Activity activity, DialogInterface.OnClickListener onDelete, int totalEntries) {
String title, message;
if (totalEntries > 1) {
title = activity.getString(R.string.delete_entries);
message = String.format(activity.getString(R.string.delete_entries_description), totalEntries);
} else {
title = activity.getString(R.string.delete_entry);
message = activity.getString(R.string.delete_entry_description);
}
showSecureDialog(new AlertDialog.Builder(activity)
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.yes, onDelete)
.setNegativeButton(android.R.string.no, null)
.create());
}
public static void showDiscardDialog(Activity activity, DialogInterface.OnClickListener onSave, DialogInterface.OnClickListener onDiscard) { public static void showDiscardDialog(Activity activity, DialogInterface.OnClickListener onSave, DialogInterface.OnClickListener onDiscard) {
showSecureDialog(new AlertDialog.Builder(activity) showSecureDialog(new AlertDialog.Builder(activity)
.setTitle(activity.getString(R.string.discard_changes)) .setTitle(activity.getString(R.string.discard_changes))

View file

@ -73,7 +73,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private String _selectedGroup; private String _selectedGroup;
private boolean _searchSubmitted; private boolean _searchSubmitted;
private VaultEntry _selectedEntry; private List<VaultEntry> _selectedEntries;
private ActionMode _actionMode; private ActionMode _actionMode;
private Menu _menu; private Menu _menu;
@ -123,6 +123,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}); });
_fabScrollHelper = new FabScrollHelper(_fabMenu); _fabScrollHelper = new FabScrollHelper(_fabMenu);
_selectedEntries = new ArrayList<>();
} }
@Override @Override
@ -502,6 +503,15 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
super.onBackPressed(); super.onBackPressed();
} }
private void deleteEntries(List<VaultEntry> entries) {
for (VaultEntry entry: entries) {
VaultEntry oldEntry = _vault.removeEntry(entry);
_entryListView.removeEntry(oldEntry);
}
saveVault();
}
private void deleteEntry(VaultEntry entry) { private void deleteEntry(VaultEntry entry) {
VaultEntry oldEntry = _vault.removeEntry(entry); VaultEntry oldEntry = _vault.removeEntry(entry);
saveVault(); saveVault();
@ -671,9 +681,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
@Override @Override
public void onEntryClick(VaultEntry entry) { public void onEntryClick(VaultEntry entry) {
if (_selectedEntry != null) { if (_actionMode != null) {
if (_selectedEntry == entry) { if (_selectedEntries.isEmpty()) {
_actionMode.finish(); _actionMode.finish();
} else {
setIsMultipleSelected(_selectedEntries.size() > 1);
} }
return; return;
@ -682,13 +694,29 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
copyEntryCode(entry); copyEntryCode(entry);
} }
@Override
public void onSelect(VaultEntry entry) {
_selectedEntries.add(entry);
}
@Override
public void onDeselect(VaultEntry entry) {
_selectedEntries.remove(entry);
}
private void setIsMultipleSelected(boolean multipleSelected) {
_entryListView.setIsLongPressDragEnabled(!multipleSelected);
_actionMode.getMenu().findItem(R.id.action_edit).setVisible(!multipleSelected);
_actionMode.getMenu().findItem(R.id.action_copy).setVisible(!multipleSelected);
}
@Override @Override
public void onLongEntryClick(VaultEntry entry) { public void onLongEntryClick(VaultEntry entry) {
if (_selectedEntry != null) { if (!_selectedEntries.isEmpty()) {
return; return;
} }
_selectedEntry = entry; _selectedEntries.add(entry);
_entryListView.setActionModeState(true, entry); _entryListView.setActionModeState(true, entry);
_actionMode = this.startSupportActionMode(_actionModeCallbacks); _actionMode = this.startSupportActionMode(_actionModeCallbacks);
} }
@ -753,26 +781,29 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_copy: case R.id.action_copy:
copyEntryCode(_selectedEntry); copyEntryCode(_selectedEntries.get(0));
mode.finish(); mode.finish();
return true; return true;
case R.id.action_edit: case R.id.action_edit:
startEditProfileActivity(CODE_EDIT_ENTRY, _selectedEntry, false); startEditProfileActivity(CODE_EDIT_ENTRY, _selectedEntries.get(0), false);
mode.finish(); mode.finish();
return true; return true;
case R.id.action_delete: case R.id.action_delete:
Dialogs.showDeleteEntryDialog(MainActivity.this, (d, which) -> { Dialogs.showDeleteEntriesDialog(MainActivity.this, (d, which) -> {
deleteEntry(_selectedEntry); deleteEntries(_selectedEntries);
if (_selectedEntry.getGroup() != null) { for (VaultEntry entry : _selectedEntries) {
if (!_vault.getGroups().contains(_selectedEntry.getGroup())) { if (entry.getGroup() != null) {
updateGroupFilterMenu(); if (!_vault.getGroups().contains(entry.getGroup())) {
updateGroupFilterMenu();
}
} }
} }
mode.finish(); mode.finish();
}); }, _selectedEntries.size());
return true; return true;
default: default:
return false; return false;
@ -782,7 +813,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
@Override @Override
public void onDestroyActionMode(ActionMode mode) { public void onDestroyActionMode(ActionMode mode) {
_entryListView.setActionModeState(false, null); _entryListView.setActionModeState(false, null);
_selectedEntry = null; _selectedEntries.clear();
_actionMode = null; _actionMode = null;
} }
} }

View file

@ -26,7 +26,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
private EntryListView _view; private EntryListView _view;
private List<VaultEntry> _entries; private List<VaultEntry> _entries;
private List<VaultEntry> _shownEntries; private List<VaultEntry> _shownEntries;
private VaultEntry _selectedEntry; private List<VaultEntry> _selectedEntries;
private VaultEntry _focusedEntry; private VaultEntry _focusedEntry;
private boolean _showAccountName; private boolean _showAccountName;
private boolean _searchAccountName; private boolean _searchAccountName;
@ -46,6 +46,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
public EntryAdapter(EntryListView view) { public EntryAdapter(EntryListView view) {
_entries = new ArrayList<>(); _entries = new ArrayList<>();
_shownEntries = new ArrayList<>(); _shownEntries = new ArrayList<>();
_selectedEntries = new ArrayList<>();
_holders = new ArrayList<>(); _holders = new ArrayList<>();
_dimHandler = new Handler(); _dimHandler = new Handler();
_view = view; _view = view;
@ -302,7 +303,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
@Override @Override
public void onBindViewHolder(final EntryHolder holder, int position) { public void onBindViewHolder(final EntryHolder holder, int position) {
VaultEntry entry = _shownEntries.get(position); VaultEntry entry = _shownEntries.get(position);
holder.setFocused(entry == _selectedEntry); holder.setFocused(_selectedEntries.contains(entry));
boolean hidden = _tapToReveal && entry != _focusedEntry; boolean hidden = _tapToReveal && entry != _focusedEntry;
boolean dimmed = _highlightEntry && _focusedEntry != null && _focusedEntry != entry; boolean dimmed = _highlightEntry && _focusedEntry != null && _focusedEntry != entry;
@ -315,7 +316,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
public void onClick(View v) { public void onClick(View v) {
boolean handled = false; boolean handled = false;
if (_selectedEntry == null) { if (_selectedEntries.isEmpty()) {
if (_highlightEntry || _tapToReveal) { if (_highlightEntry || _tapToReveal) {
if (_focusedEntry == entry) { if (_focusedEntry == entry) {
resetFocus(); resetFocus();
@ -324,6 +325,16 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
focusEntry(entry); focusEntry(entry);
} }
} }
} else {
if (_selectedEntries.contains(entry)) {
_view.onDeselect(entry);
removeSelectedEntry(entry);
holder.setFocused(false);
} else {
holder.setFocused(true);
addSelectedEntry(entry);
_view.onSelect(entry);
}
} }
if (!handled) { if (!handled) {
@ -335,8 +346,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
@Override @Override
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
int position = holder.getAdapterPosition(); int position = holder.getAdapterPosition();
if (_selectedEntry == null) { if (_selectedEntries.isEmpty()) {
setSelectedEntry(_shownEntries.get(position));
holder.setFocused(true); holder.setFocused(true);
} }
@ -444,14 +454,23 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_focusedEntry = null; _focusedEntry = null;
} }
public void setSelectedEntry(VaultEntry entry) { public void removeSelectedEntry(VaultEntry entry) {
_selectedEntries.remove(entry);
}
public void addSelectedEntry(VaultEntry entry) {
if (entry == null) { if (entry == null) {
notifyItemChanged(_shownEntries.indexOf(_selectedEntry)); for (VaultEntry vaultEntry: _selectedEntries) {
notifyItemChanged(_shownEntries.indexOf(vaultEntry));
}
_selectedEntries.clear();
return;
} else if (_highlightEntry) { } else if (_highlightEntry) {
resetFocus(); resetFocus();
} }
_selectedEntry = entry; _selectedEntries.add(entry);
} }
public boolean isDragAndDropAllowed() { public boolean isDragAndDropAllowed() {
@ -474,5 +493,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
void onEntryDrop(VaultEntry entry); void onEntryDrop(VaultEntry entry);
void onEntryChange(VaultEntry entry); void onEntryChange(VaultEntry entry);
void onPeriodUniformityChanged(boolean uniform); void onPeriodUniformityChanged(boolean uniform);
void onSelect(VaultEntry entry);
void onDeselect(VaultEntry entry);
} }
} }

View file

@ -132,10 +132,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
} }
} }
public void setIsLongPressDragEnabled(boolean enabled) {
_touchCallback.setIsLongPressDragEnabled(enabled);
}
public void setActionModeState(boolean enabled, VaultEntry entry) { public void setActionModeState(boolean enabled, VaultEntry entry) {
_touchCallback.setSelectedEntry(entry); _touchCallback.setSelectedEntry(entry);
_touchCallback.setIsLongPressDragEnabled(enabled && _adapter.isDragAndDropAllowed()); _touchCallback.setIsLongPressDragEnabled(enabled && _adapter.isDragAndDropAllowed());
_adapter.setSelectedEntry(entry); _adapter.addSelectedEntry(entry);
} }
public void setSortCategory(SortCategory sortCategory, boolean apply) { public void setSortCategory(SortCategory sortCategory, boolean apply) {
@ -194,6 +198,12 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_listener.onEntryChange(entry); _listener.onEntryChange(entry);
} }
@Override
public void onSelect(VaultEntry entry) { _listener.onSelect(entry); }
@Override
public void onDeselect(VaultEntry entry) { _listener.onDeselect(entry); }
@Override @Override
public void onPeriodUniformityChanged(boolean isUniform) { public void onPeriodUniformityChanged(boolean isUniform) {
setShowProgress(isUniform); setShowProgress(isUniform);
@ -298,6 +308,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
void onEntryChange(VaultEntry entry); void onEntryChange(VaultEntry entry);
void onLongEntryClick(VaultEntry entry); void onLongEntryClick(VaultEntry entry);
void onScroll(int dx, int dy); void onScroll(int dx, int dy);
void onSelect(VaultEntry entry);
void onDeselect(VaultEntry entry);
} }
private class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration { private class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {

View file

@ -92,6 +92,8 @@
<string name="encrypting_vault">Encrypting the vault</string> <string name="encrypting_vault">Encrypting the vault</string>
<string name="delete_entry">Delete entry</string> <string name="delete_entry">Delete entry</string>
<string name="delete_entry_description">Are you sure you want to delete this entry?</string> <string name="delete_entry_description">Are you sure you want to delete this entry?</string>
<string name="delete_entries">Delete entries</string>
<string name="delete_entries_description">Are you sure you want to delete %d entries?</string>
<string name="discard_changes">Discard changes?</string> <string name="discard_changes">Discard changes?</string>
<string name="discard_changes_description">Your changes have not been saved</string> <string name="discard_changes_description">Your changes have not been saved</string>
<string name="folder">Folder</string> <string name="folder">Folder</string>