From 2ce259255d1678706b649b4bf4cf55af880e9257 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Tue, 11 Dec 2018 11:44:36 +0100 Subject: [PATCH] Add a 'group' field to entries for filtering from the main view --- .../java/me/impy/aegis/db/DatabaseEntry.java | 13 +++ .../me/impy/aegis/db/DatabaseManager.java | 13 +++ .../SimpleItemTouchHelperCallback.java | 7 +- .../me/impy/aegis/helpers/SpinnerHelper.java | 11 +++ .../main/java/me/impy/aegis/ui/Dialogs.java | 21 +++++ .../me/impy/aegis/ui/EditEntryActivity.java | 69 +++++++++++++++ .../java/me/impy/aegis/ui/MainActivity.java | 62 +++++++++++++- .../me/impy/aegis/ui/views/EntryAdapter.java | 85 ++++++++++++++++--- .../me/impy/aegis/ui/views/EntryListView.java | 11 ++- .../drawable/ic_baseline_filter_list_24dp.xml | 9 ++ .../main/res/layout/activity_edit_entry.xml | 26 ++++-- app/src/main/res/menu/menu_main.xml | 18 +++- app/src/main/res/values/strings.xml | 7 ++ 13 files changed, 327 insertions(+), 25 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_filter_list_24dp.xml diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java b/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java index 7d9ffbff..255b19dd 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java @@ -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()); } diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java index 1bb692bf..8699dbe1 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java @@ -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 getGroups() { + TreeSet 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; diff --git a/app/src/main/java/me/impy/aegis/helpers/SimpleItemTouchHelperCallback.java b/app/src/main/java/me/impy/aegis/helpers/SimpleItemTouchHelperCallback.java index dddd1937..c7bba098 100644 --- a/app/src/main/java/me/impy/aegis/helpers/SimpleItemTouchHelperCallback.java +++ b/app/src/main/java/me/impy/aegis/helpers/SimpleItemTouchHelperCallback.java @@ -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 diff --git a/app/src/main/java/me/impy/aegis/helpers/SpinnerHelper.java b/app/src/main/java/me/impy/aegis/helpers/SpinnerHelper.java index ac6b9df3..0f803ef9 100644 --- a/app/src/main/java/me/impy/aegis/helpers/SpinnerHelper.java +++ b/app/src/main/java/me/impy/aegis/helpers/SpinnerHelper.java @@ -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 adapter = ArrayAdapter.createFromResource(context, textArrayResId, android.R.layout.simple_spinner_item); + initSpinner(spinner, adapter); + } + + public static void fillSpinner(Context context, Spinner spinner, List 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(); diff --git a/app/src/main/java/me/impy/aegis/ui/Dialogs.java b/app/src/main/java/me/impy/aegis/ui/Dialogs.java index 408bf00d..a0956550 100644 --- a/app/src/main/java/me/impy/aegis/ui/Dialogs.java +++ b/app/src/main/java/me/impy/aegis/ui/Dialogs.java @@ -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); diff --git a/app/src/main/java/me/impy/aegis/ui/EditEntryActivity.java b/app/src/main/java/me/impy/aegis/ui/EditEntryActivity.java index 6d6a4260..95e81850 100644 --- a/app/src/main/java/me/impy/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/EditEntryActivity.java @@ -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 _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 _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(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 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(); diff --git a/app/src/main/java/me/impy/aegis/ui/MainActivity.java b/app/src/main/java/me/impy/aegis/ui/MainActivity.java index 9297be1d..94002efb 100644 --- a/app/src/main/java/me/impy/aegis/ui/MainActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/MainActivity.java @@ -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 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); } } diff --git a/app/src/main/java/me/impy/aegis/ui/views/EntryAdapter.java b/app/src/main/java/me/impy/aegis/ui/views/EntryAdapter.java index 73ca1568..0cfed2da 100644 --- a/app/src/main/java/me/impy/aegis/ui/views/EntryAdapter.java +++ b/app/src/main/java/me/impy/aegis/ui/views/EntryAdapter.java @@ -20,14 +20,17 @@ import me.impy.aegis.otp.TotpInfo; public class EntryAdapter extends RecyclerView.Adapter implements ItemTouchHelperAdapter { private List _entries; + private List _shownEntries; private static Listener _listener; private boolean _showAccountName; + private String _groupFilter; // keeps track of the viewholders that are currently bound private List _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 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 implements I public void addEntries(List 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 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 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 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 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 implements I public int getUniformPeriod() { List 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 implements I @Override public int getItemCount() { - return _entries.size(); + return _shownEntries.size(); } public interface Listener { diff --git a/app/src/main/java/me/impy/aegis/ui/views/EntryListView.java b/app/src/main/java/me/impy/aegis/ui/views/EntryListView.java index 508078c2..41e573de 100644 --- a/app/src/main/java/me/impy/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/me/impy/aegis/ui/views/EntryListView.java @@ -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(); diff --git a/app/src/main/res/drawable/ic_baseline_filter_list_24dp.xml b/app/src/main/res/drawable/ic_baseline_filter_list_24dp.xml new file mode 100644 index 00000000..2fa2dc86 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_filter_list_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_edit_entry.xml b/app/src/main/res/layout/activity_edit_entry.xml index d36084bd..25c8d2d1 100644 --- a/app/src/main/res/layout/activity_edit_entry.xml +++ b/app/src/main/res/layout/activity_edit_entry.xml @@ -74,7 +74,7 @@ - + android:layout_weight="1" + android:orientation="horizontal" + android:gravity="center_vertical"> + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index e5b53784..e94923d8 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -6,7 +6,23 @@ android:id="@+id/action_lock" android:icon="@drawable/ic_lock" app:showAsAction="ifRoom" - android:title=""/> + android:title="@string/lock"/> + + + + + + + Remove slot Are you sure you want to remove this slot? An error occurred while trying to add a new slot: + Filter + Lock + All + Name + No group + New group + Enter a group name