mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-20 22:09:12 +00:00
Add a 'group' field to entries for filtering from the main view
This commit is contained in:
parent
d0e60cec75
commit
2ce259255d
13 changed files with 327 additions and 25 deletions
|
@ -5,6 +5,7 @@ import org.json.JSONObject;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.impy.aegis.encoding.Base64;
|
||||
|
@ -17,6 +18,7 @@ public class DatabaseEntry implements Serializable {
|
|||
private UUID _uuid;
|
||||
private String _name = "";
|
||||
private String _issuer = "";
|
||||
private String _group;
|
||||
private OtpInfo _info;
|
||||
private byte[] _icon;
|
||||
|
||||
|
@ -47,6 +49,7 @@ public class DatabaseEntry implements Serializable {
|
|||
obj.put("uuid", _uuid.toString());
|
||||
obj.put("name", _name);
|
||||
obj.put("issuer", _issuer);
|
||||
obj.put("group", _group);
|
||||
obj.put("icon", _icon == null ? JSONObject.NULL : Base64.encode(_icon));
|
||||
obj.put("info", _info.toJson());
|
||||
} catch (JSONException e) {
|
||||
|
@ -69,6 +72,7 @@ public class DatabaseEntry implements Serializable {
|
|||
DatabaseEntry entry = new DatabaseEntry(uuid, info);
|
||||
entry.setName(obj.getString("name"));
|
||||
entry.setIssuer(obj.getString("issuer"));
|
||||
entry.setGroup(obj.optString("group", null));
|
||||
|
||||
Object icon = obj.get("icon");
|
||||
if (icon != JSONObject.NULL) {
|
||||
|
@ -94,6 +98,10 @@ public class DatabaseEntry implements Serializable {
|
|||
return _issuer;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return _group;
|
||||
}
|
||||
|
||||
public byte[] getIcon() {
|
||||
return _icon;
|
||||
}
|
||||
|
@ -110,6 +118,10 @@ public class DatabaseEntry implements Serializable {
|
|||
_issuer = issuer;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
_group = group;
|
||||
}
|
||||
|
||||
public void setInfo(OtpInfo info) {
|
||||
_info = info;
|
||||
}
|
||||
|
@ -135,6 +147,7 @@ public class DatabaseEntry implements Serializable {
|
|||
return getUUID().equals(entry.getUUID())
|
||||
&& getName().equals(entry.getName())
|
||||
&& getIssuer().equals(entry.getIssuer())
|
||||
&& Objects.equals(getGroup(), entry.getGroup())
|
||||
&& getInfo().equals(entry.getInfo())
|
||||
&& Arrays.equals(getIcon(), entry.getIcon());
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.Collator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.impy.aegis.BuildConfig;
|
||||
|
@ -159,6 +161,17 @@ public class DatabaseManager {
|
|||
return _db.getEntryByUUID(uuid);
|
||||
}
|
||||
|
||||
public TreeSet<String> getGroups() {
|
||||
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
||||
for (DatabaseEntry entry : getEntries()) {
|
||||
String group = entry.getGroup();
|
||||
if (group != null) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
public DatabaseFileCredentials getCredentials() {
|
||||
assertState(false, true);
|
||||
return _creds;
|
||||
|
|
|
@ -7,6 +7,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||
|
||||
private final ItemTouchHelperAdapter _adapter;
|
||||
private boolean _positionChanged = false;
|
||||
private boolean _isLongPressDragEnabled = true;
|
||||
|
||||
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
|
||||
_adapter = adapter;
|
||||
|
@ -14,7 +15,11 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return true;
|
||||
return _isLongPressDragEnabled;
|
||||
}
|
||||
|
||||
public void setIsLongPressDragEnabled(boolean enabled) {
|
||||
_isLongPressDragEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,6 +5,8 @@ import androidx.annotation.ArrayRes;
|
|||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SpinnerHelper {
|
||||
private SpinnerHelper() {
|
||||
|
||||
|
@ -12,6 +14,15 @@ public class SpinnerHelper {
|
|||
|
||||
public static void fillSpinner(Context context, Spinner spinner, @ArrayRes int textArrayResId) {
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, textArrayResId, android.R.layout.simple_spinner_item);
|
||||
initSpinner(spinner, adapter);
|
||||
}
|
||||
|
||||
public static <T> void fillSpinner(Context context, Spinner spinner, List<T> items) {
|
||||
ArrayAdapter adapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, items);
|
||||
initSpinner(spinner, adapter);
|
||||
}
|
||||
|
||||
private static void initSpinner(Spinner spinner, ArrayAdapter adapter) {
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.invalidate();
|
||||
|
|
|
@ -2,6 +2,7 @@ package me.impy.aegis.ui;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.text.Editable;
|
||||
|
@ -19,6 +20,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import me.impy.aegis.R;
|
||||
|
@ -47,6 +49,21 @@ public class Dialogs {
|
|||
dialog.show();
|
||||
}
|
||||
|
||||
public static void showTextInputDialog(Context context, @StringRes int titleId, TextInputListener listener) {
|
||||
EditText input = new EditText(context);
|
||||
|
||||
showSecureDialog(new AlertDialog.Builder(context)
|
||||
.setTitle(titleId)
|
||||
.setView(input)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
listener.onTextInputResult(input.getText().toString());
|
||||
}
|
||||
})
|
||||
.create());
|
||||
}
|
||||
|
||||
public static void showDeleteEntryDialog(Activity activity, DialogInterface.OnClickListener onDelete) {
|
||||
showSecureDialog(new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.delete_entry))
|
||||
|
@ -165,6 +182,10 @@ public class Dialogs {
|
|||
showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
public interface TextInputListener {
|
||||
void onTextInputResult(String text);
|
||||
}
|
||||
|
||||
public interface SlotListener {
|
||||
void onSlotResult(Slot slot, Cipher cipher);
|
||||
void onException(Exception e);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package me.impy.aegis.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
|
@ -36,6 +38,11 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
@ -57,6 +64,7 @@ public class EditEntryActivity extends AegisActivity {
|
|||
|
||||
private boolean _isNew = false;
|
||||
private DatabaseEntry _origEntry;
|
||||
private TreeSet<String> _groups;
|
||||
private boolean _hasCustomIcon = false;
|
||||
// keep track of icon changes separately as the generated jpeg's are not deterministic
|
||||
private boolean _hasChangedIcon = false;
|
||||
|
@ -75,6 +83,8 @@ public class EditEntryActivity extends AegisActivity {
|
|||
private Spinner _spinnerType;
|
||||
private Spinner _spinnerAlgo;
|
||||
private Spinner _spinnerDigits;
|
||||
private Spinner _spinnerGroup;
|
||||
private List<String> _spinnerGroupList = new ArrayList<>();
|
||||
|
||||
private KropView _kropView;
|
||||
|
||||
|
@ -94,6 +104,8 @@ public class EditEntryActivity extends AegisActivity {
|
|||
Intent intent = getIntent();
|
||||
_origEntry = (DatabaseEntry) intent.getSerializableExtra("entry");
|
||||
_isNew = intent.getBooleanExtra("isNew", false);
|
||||
_groups = new TreeSet<String>(Collator.getInstance());
|
||||
_groups.addAll(intent.getStringArrayListExtra("groups"));
|
||||
if (_isNew) {
|
||||
setTitle(R.string.add_new_profile);
|
||||
}
|
||||
|
@ -115,6 +127,9 @@ public class EditEntryActivity extends AegisActivity {
|
|||
SpinnerHelper.fillSpinner(this, _spinnerAlgo, R.array.otp_algo_array);
|
||||
_spinnerDigits = findViewById(R.id.spinner_digits);
|
||||
SpinnerHelper.fillSpinner(this, _spinnerDigits, R.array.otp_digits_array);
|
||||
_spinnerGroup = findViewById(R.id.spinner_group);
|
||||
updateGroupSpinnerList();
|
||||
SpinnerHelper.fillSpinner(this, _spinnerGroup, _spinnerGroupList);
|
||||
|
||||
_advancedSettingsHeader = findViewById(R.id.accordian_header);
|
||||
_advancedSettings = findViewById(R.id.expandableLayout);
|
||||
|
@ -159,6 +174,12 @@ public class EditEntryActivity extends AegisActivity {
|
|||
|
||||
String digits = Integer.toString(_origEntry.getInfo().getDigits());
|
||||
_spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false);
|
||||
|
||||
String group = _origEntry.getGroup();
|
||||
if (group != null) {
|
||||
int pos = _groups.contains(group) ? _groups.headSet(group).size() : -1;
|
||||
_spinnerGroup.setSelection(pos + 1, false);
|
||||
}
|
||||
}
|
||||
|
||||
// update the icon if the text changed
|
||||
|
@ -191,6 +212,38 @@ public class EditEntryActivity extends AegisActivity {
|
|||
}
|
||||
});
|
||||
|
||||
final Activity activity = this;
|
||||
_spinnerGroup.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
private int prevPosition;
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position == _spinnerGroupList.size() - 1) {
|
||||
Dialogs.showTextInputDialog(activity, R.string.enter_group_name, new Dialogs.TextInputListener() {
|
||||
@Override
|
||||
public void onTextInputResult(String text) {
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
_groups.add(text);
|
||||
// reset the selection to "No group" to work around a quirk
|
||||
_spinnerGroup.setSelection(0, false);
|
||||
updateGroupSpinnerList();
|
||||
_spinnerGroup.setSelection(_spinnerGroupList.indexOf(text), false);
|
||||
}
|
||||
});
|
||||
_spinnerGroup.setSelection(prevPosition, false);
|
||||
} else {
|
||||
prevPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
_iconView.setOnClickListener(v -> {
|
||||
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
|
||||
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
|
||||
|
@ -262,6 +315,14 @@ public class EditEntryActivity extends AegisActivity {
|
|||
});
|
||||
}
|
||||
|
||||
private void updateGroupSpinnerList() {
|
||||
Resources res = getResources();
|
||||
_spinnerGroupList.clear();
|
||||
_spinnerGroupList.add(res.getString(R.string.no_group));
|
||||
_spinnerGroupList.addAll(_groups);
|
||||
_spinnerGroupList.add(res.getString(R.string.new_group));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
AtomicReference<String> msg = new AtomicReference<>();
|
||||
|
@ -437,6 +498,14 @@ public class EditEntryActivity extends AegisActivity {
|
|||
entry.setIssuer(_textIssuer.getText().toString());
|
||||
entry.setName(_textName.getText().toString());
|
||||
|
||||
int groupPos = _spinnerGroup.getSelectedItemPosition();
|
||||
if (groupPos != 0) {
|
||||
String group = _spinnerGroupList.get(_spinnerGroup.getSelectedItemPosition());
|
||||
entry.setGroup(group);
|
||||
} else {
|
||||
entry.setGroup(null);
|
||||
}
|
||||
|
||||
if (_hasChangedIcon) {
|
||||
if (_hasCustomIcon) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
|
|
@ -11,12 +11,15 @@ import android.os.Bundle;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SubMenu;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getbase.floatingactionbutton.FloatingActionsMenu;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import me.impy.aegis.AegisApplication;
|
||||
import me.impy.aegis.R;
|
||||
|
@ -43,6 +46,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
private AegisApplication _app;
|
||||
private DatabaseManager _db;
|
||||
private boolean _loaded;
|
||||
private String _checkedGroup;
|
||||
|
||||
private Menu _menu;
|
||||
private FloatingActionsMenu _fabMenu;
|
||||
|
@ -164,6 +168,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
intent.putExtra("entry", entry);
|
||||
}
|
||||
intent.putExtra("isNew", isNew);
|
||||
intent.putExtra("groups", new ArrayList<>(_db.getGroups()));
|
||||
startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
|
@ -185,14 +190,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
private void onEditEntryResult(int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
DatabaseEntry entry = (DatabaseEntry) data.getSerializableExtra("entry");
|
||||
if (!data.getBooleanExtra("delete", false)) {
|
||||
if (data.getBooleanExtra("delete", false)) {
|
||||
deleteEntry(entry);
|
||||
} else {
|
||||
// this profile has been serialized/deserialized and is no longer the same instance it once was
|
||||
// to deal with this, the replaceEntry functions are used
|
||||
_db.replaceEntry(entry);
|
||||
_entryListView.replaceEntry(entry);
|
||||
saveDatabase();
|
||||
} else {
|
||||
deleteEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,6 +210,38 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
}
|
||||
}
|
||||
|
||||
private void updateGroupFilterMenu() {
|
||||
SubMenu menu = _menu.findItem(R.id.action_filter).getSubMenu();
|
||||
for (int i = menu.size() - 1; i >= 0; i--) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
if (item.getItemId() == R.id.menu_filter_all) {
|
||||
continue;
|
||||
}
|
||||
menu.removeItem(item.getItemId());
|
||||
}
|
||||
|
||||
// if the group no longer exists, switch back to 'All'
|
||||
TreeSet<String> groups = _db.getGroups();
|
||||
if (_checkedGroup != null && !groups.contains(_checkedGroup)) {
|
||||
menu.findItem(R.id.menu_filter_all).setChecked(true);
|
||||
setGroupFilter(null);
|
||||
}
|
||||
|
||||
for (String group : groups) {
|
||||
MenuItem item = menu.add(R.id.action_filter_group, Menu.NONE, Menu.NONE, group);
|
||||
if (group.equals(_checkedGroup)) {
|
||||
item.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
menu.setGroupCheckable(R.id.action_filter_group, true, true);
|
||||
}
|
||||
|
||||
private void setGroupFilter(String group) {
|
||||
_checkedGroup = group;
|
||||
_entryListView.setGroupFilter(group);
|
||||
}
|
||||
|
||||
private void addEntry(DatabaseEntry entry) {
|
||||
_db.addEntry(entry);
|
||||
_entryListView.addEntry(entry);
|
||||
|
@ -281,6 +318,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
unlockDatabase(null);
|
||||
}
|
||||
} else if (_loaded) {
|
||||
// update the list of groups in the filter menu
|
||||
if (_menu != null) {
|
||||
updateGroupFilterMenu();
|
||||
}
|
||||
|
||||
// refresh all codes to prevent showing old ones
|
||||
_entryListView.refresh(true);
|
||||
} else {
|
||||
|
@ -310,6 +352,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
dialog.dismiss();
|
||||
Dialogs.showDeleteEntryDialog(this, (d, which) -> {
|
||||
deleteEntry(entry);
|
||||
// update the filter list if the group no longer exists
|
||||
if (!_db.getGroups().contains(entry.getGroup())) {
|
||||
updateGroupFilterMenu();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -333,6 +379,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
_menu = menu;
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
updateLockIcon();
|
||||
updateGroupFilterMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -347,6 +394,15 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
lockDatabase();
|
||||
return true;
|
||||
default:
|
||||
if (item.getGroupId() == R.id.action_filter_group) {
|
||||
item.setChecked(true);
|
||||
|
||||
String group = null;
|
||||
if (item.getItemId() != R.id.menu_filter_all) {
|
||||
group = item.getTitle().toString();
|
||||
}
|
||||
setGroupFilter(group);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,17 @@ import me.impy.aegis.otp.TotpInfo;
|
|||
|
||||
public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements ItemTouchHelperAdapter {
|
||||
private List<DatabaseEntry> _entries;
|
||||
private List<DatabaseEntry> _shownEntries;
|
||||
private static Listener _listener;
|
||||
private boolean _showAccountName;
|
||||
private String _groupFilter;
|
||||
|
||||
// keeps track of the viewholders that are currently bound
|
||||
private List<EntryHolder> _holders;
|
||||
|
||||
public EntryAdapter(Listener listener) {
|
||||
_entries = new ArrayList<>();
|
||||
_shownEntries = new ArrayList<>();
|
||||
_holders = new ArrayList<>();
|
||||
_listener = listener;
|
||||
}
|
||||
|
@ -38,6 +41,9 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
_entries.add(entry);
|
||||
if (!isEntryFiltered(entry)) {
|
||||
_shownEntries.add(entry);
|
||||
}
|
||||
|
||||
int position = getItemCount() - 1;
|
||||
if (position == 0) {
|
||||
|
@ -49,26 +55,59 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
|
||||
public void addEntries(List<DatabaseEntry> entries) {
|
||||
_entries.addAll(entries);
|
||||
for (DatabaseEntry entry : entries) {
|
||||
if (!isEntryFiltered(entry)) {
|
||||
_shownEntries.add(entry);
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
entry = getEntryByUUID(entry.getUUID());
|
||||
int position = _entries.indexOf(entry);
|
||||
_entries.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
_entries.remove(entry);
|
||||
|
||||
if (_shownEntries.contains(entry)) {
|
||||
int position = _shownEntries.indexOf(entry);
|
||||
_shownEntries.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearEntries() {
|
||||
_entries.clear();
|
||||
_shownEntries.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void replaceEntry(DatabaseEntry newEntry) {
|
||||
DatabaseEntry oldEntry = getEntryByUUID(newEntry.getUUID());
|
||||
int position = _entries.indexOf(oldEntry);
|
||||
_entries.set(position, newEntry);
|
||||
notifyItemChanged(position);
|
||||
_entries.set(_entries.indexOf(oldEntry), newEntry);
|
||||
|
||||
if (_shownEntries.contains(oldEntry)) {
|
||||
int position = _shownEntries.indexOf(oldEntry);
|
||||
if (isEntryFiltered(newEntry)) {
|
||||
_shownEntries.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
} else {
|
||||
_shownEntries.set(position, newEntry);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
} else if (!isEntryFiltered(newEntry)) {
|
||||
// TODO: preserve order
|
||||
_shownEntries.add(newEntry);
|
||||
|
||||
int position = getItemCount() - 1;
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEntryFiltered(DatabaseEntry entry) {
|
||||
String group = entry.getGroup();
|
||||
if (_groupFilter == null) {
|
||||
return false;
|
||||
}
|
||||
return group == null || !group.equals(_groupFilter);
|
||||
}
|
||||
|
||||
private DatabaseEntry getEntryByUUID(UUID uuid) {
|
||||
|
@ -90,6 +129,17 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
}
|
||||
}
|
||||
|
||||
public void setGroupFilter(String group) {
|
||||
_groupFilter = group;
|
||||
_shownEntries.clear();
|
||||
for (DatabaseEntry entry : _entries) {
|
||||
if (!isEntryFiltered(entry)) {
|
||||
_shownEntries.add(entry);
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDismiss(int position) {
|
||||
|
||||
|
@ -97,16 +147,27 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
|
||||
@Override
|
||||
public void onItemDrop(int position) {
|
||||
_listener.onEntryDrop(_entries.get(position));
|
||||
// moving entries is not allowed when a filter is applied
|
||||
if (_groupFilter != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_listener.onEntryDrop(_shownEntries.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemMove(int firstPosition, int secondPosition) {
|
||||
// moving entries is not allowed when a filter is applied
|
||||
if (_groupFilter != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// notify the database first
|
||||
_listener.onEntryMove(_entries.get(firstPosition), _entries.get(secondPosition));
|
||||
|
||||
// update our side of things
|
||||
Collections.swap(_entries, firstPosition, secondPosition);
|
||||
Collections.swap(_shownEntries, firstPosition, secondPosition);
|
||||
notifyItemMoved(firstPosition, secondPosition);
|
||||
}
|
||||
|
||||
|
@ -124,7 +185,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
|
||||
@Override
|
||||
public void onBindViewHolder(final EntryHolder holder, int position) {
|
||||
DatabaseEntry entry = _entries.get(position);
|
||||
DatabaseEntry entry = _shownEntries.get(position);
|
||||
boolean showProgress = !isPeriodUniform() && entry.getInfo() instanceof TotpInfo;
|
||||
holder.setData(entry, _showAccountName, showProgress);
|
||||
if (showProgress) {
|
||||
|
@ -135,14 +196,14 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = holder.getAdapterPosition();
|
||||
_listener.onEntryClick(_entries.get(position));
|
||||
_listener.onEntryClick(_shownEntries.get(position));
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
int position = holder.getAdapterPosition();
|
||||
return _listener.onLongEntryClick(_entries.get(position));
|
||||
return _listener.onLongEntryClick(_shownEntries.get(position));
|
||||
}
|
||||
});
|
||||
holder.setOnRefreshClickListener(new View.OnClickListener() {
|
||||
|
@ -169,7 +230,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
|
||||
public int getUniformPeriod() {
|
||||
List<TotpInfo> infos = new ArrayList<>();
|
||||
for (DatabaseEntry entry : _entries) {
|
||||
for (DatabaseEntry entry : _shownEntries) {
|
||||
OtpInfo info = entry.getInfo();
|
||||
if (info instanceof TotpInfo) {
|
||||
infos.add((TotpInfo) info);
|
||||
|
@ -196,7 +257,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return _entries.size();
|
||||
return _shownEntries.size();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
|
|
|
@ -21,6 +21,7 @@ import me.impy.aegis.otp.TotpInfo;
|
|||
public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
private EntryAdapter _adapter;
|
||||
private Listener _listener;
|
||||
private SimpleItemTouchHelperCallback _touchCallback;
|
||||
|
||||
private PeriodProgressBar _progressBar;
|
||||
private boolean _showProgress;
|
||||
|
@ -46,8 +47,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
RecyclerView rvKeyProfiles = view.findViewById(R.id.rvKeyProfiles);
|
||||
LinearLayoutManager mLayoutManager = new LinearLayoutManager(view.getContext());
|
||||
rvKeyProfiles.setLayoutManager(mLayoutManager);
|
||||
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(_adapter);
|
||||
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
|
||||
_touchCallback = new SimpleItemTouchHelperCallback(_adapter);
|
||||
ItemTouchHelper touchHelper = new ItemTouchHelper(_touchCallback);
|
||||
touchHelper.attachToRecyclerView(rvKeyProfiles);
|
||||
rvKeyProfiles.setAdapter(_adapter);
|
||||
|
||||
|
@ -66,6 +67,12 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
return view;
|
||||
}
|
||||
|
||||
public void setGroupFilter(String group) {
|
||||
_adapter.setGroupFilter(group);
|
||||
_touchCallback.setIsLongPressDragEnabled(group == null);
|
||||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
public void refresh(boolean hard) {
|
||||
if (_showProgress) {
|
||||
_progressBar.refresh();
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
|
||||
</vector>
|
|
@ -74,7 +74,7 @@
|
|||
|
||||
<EditText android:layout_column="1"
|
||||
android:id="@+id/text_name"
|
||||
android:hint="Name"
|
||||
android:hint="@string/name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#949494"
|
||||
|
@ -84,13 +84,27 @@
|
|||
<TableRow
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp">
|
||||
<EditText android:layout_column="1"
|
||||
android:id="@+id/text_issuer"
|
||||
android:hint="@string/issuer"
|
||||
<LinearLayout android:layout_column="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#949494"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<EditText
|
||||
android:id="@+id/text_issuer"
|
||||
android:hint="@string/issuer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#949494"
|
||||
android:layout_weight="1"/>
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/spinner_group"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:backgroundTint="#949494"
|
||||
style="@style/Base.Widget.AppCompat.Spinner.Underlined"/>
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
|
|
|
@ -6,7 +6,23 @@
|
|||
android:id="@+id/action_lock"
|
||||
android:icon="@drawable/ic_lock"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title=""/>
|
||||
android:title="@string/lock"/>
|
||||
<item
|
||||
android:id="@+id/action_filter"
|
||||
android:icon="@drawable/ic_baseline_filter_list_24dp"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/filter">
|
||||
<menu>
|
||||
<group
|
||||
android:id="@+id/action_filter_group"
|
||||
android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/menu_filter_all"
|
||||
android:title="@string/all"
|
||||
android:checked="true" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
|
|
|
@ -134,4 +134,11 @@
|
|||
<string name="remove_slot">Remove slot</string>
|
||||
<string name="remove_slot_description">Are you sure you want to remove this slot?</string>
|
||||
<string name="adding_new_slot_error">An error occurred while trying to add a new slot:</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="lock">Lock</string>
|
||||
<string name="all">All</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="no_group">No group</string>
|
||||
<string name="new_group">New group</string>
|
||||
<string name="enter_group_name">Enter a group name</string>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue