From a6bf3b7c87d5924d57aa0b8408ce2f55ad08b5a3 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Tue, 22 Dec 2020 13:29:38 +0100 Subject: [PATCH] Add bottomsheet for chips --- app/build.gradle | 2 +- .../aegis/ui/MainActivity.java | 55 ++++---------- .../aegis/ui/views/EntryAdapter.java | 31 +++++--- .../aegis/ui/views/EntryListView.java | 75 +++++++++++++++++-- app/src/main/res/color/bg_chip_color.xml | 5 ++ app/src/main/res/color/bg_chip_text_color.xml | 5 ++ .../main/res/drawable-v24/rounded_dialog.xml | 8 ++ app/src/main/res/drawable/drag_handle.xml | 13 ++++ .../drawable/ic_baseline_filter_list_24dp.xml | 1 + app/src/main/res/layout/activity_main.xml | 7 ++ app/src/main/res/layout/dialog_add_entry.xml | 3 +- .../main/res/layout/dialog_select_groups.xml | 55 ++++++++++++++ .../res/layout/fragment_entry_list_view.xml | 18 +++++ app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 41 ++++++++-- 16 files changed, 255 insertions(+), 66 deletions(-) create mode 100644 app/src/main/res/color/bg_chip_color.xml create mode 100644 app/src/main/res/color/bg_chip_text_color.xml create mode 100644 app/src/main/res/drawable-v24/rounded_dialog.xml create mode 100644 app/src/main/res/drawable/drag_handle.xml create mode 100644 app/src/main/res/layout/dialog_select_groups.xml diff --git a/app/build.gradle b/app/build.gradle index fe0bd814..1e0feb86 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -153,7 +153,7 @@ dependencies { implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" implementation "com.github.topjohnwu.libsu:io:${libsuVersion}" implementation "com.google.guava:guava:${guavaVersion}-android" - implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.protobuf:protobuf-javalite:3.14.0' implementation 'com.google.zxing:core:3.4.1' implementation "com.mikepenz:iconics-core:3.2.5" diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index 54ab4dd2..0960b7ed 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -41,6 +41,8 @@ import com.beemdevelopment.aegis.vault.VaultManager; import com.beemdevelopment.aegis.vault.VaultManagerException; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.chip.Chip; +import com.google.android.material.chip.ChipGroup; import com.google.zxing.BinaryBitmap; import com.google.zxing.ChecksumException; import com.google.zxing.FormatException; @@ -58,6 +60,7 @@ import java.util.ArrayList; import java.util.List; import java.util.TreeSet; import java.util.UUID; +import java.util.stream.Collectors; public class MainActivity extends AegisActivity implements EntryListView.Listener { // activity request codes @@ -76,7 +79,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private AegisApplication _app; private VaultManager _vault; private boolean _loaded; - private String _selectedGroup; + private List _selectedGroups; private boolean _searchSubmitted; private boolean _isAuthenticating; @@ -247,7 +250,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene } else { intent.putExtra("entryUUID", entry.getUUID()); } - intent.putExtra("selectedGroup", _selectedGroup); + intent.putExtra("selectedGroup", (ArrayList) _selectedGroups); startActivityForResult(intent, requestCode); } @@ -319,45 +322,13 @@ 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 = _vault.getGroups(); - if (_selectedGroup != null && !groups.contains(_selectedGroup)) { - 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(_selectedGroup)) { - item.setChecked(true); - } - } - - if (groups.size() > 0) { - menu.add(R.id.action_filter_group, Menu.NONE, 10, R.string.filter_ungrouped); - } - - menu.setGroupCheckable(R.id.action_filter_group, true, true); - } - private void updateSortCategoryMenu() { SortCategory category = getPreferences().getCurrentSortCategory(); _menu.findItem(category.getMenuItem()).setChecked(true); } - private void setGroupFilter(String group) { - getSupportActionBar().setSubtitle(group); - _selectedGroup = group; + private void setGroupFilter(List group) { + _selectedGroups = group; _entryListView.setGroupFilter(group, true); } @@ -492,7 +463,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene } else if (_loaded) { // update the list of groups in the filter menu if (_menu != null) { - updateGroupFilterMenu(); + _entryListView.setGroups(_vault.getGroups()); } // refresh all codes to prevent showing old ones @@ -516,7 +487,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene collapseSearchView(); setTitle("Aegis"); - setGroupFilter(_selectedGroup); + setGroupFilter(_selectedGroups); return; } @@ -543,7 +514,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene getMenuInflater().inflate(R.menu.menu_main, menu); updateLockIcon(); if (_loaded) { - updateGroupFilterMenu(); + _entryListView.setGroups(_vault.getGroups()); updateSortCategoryMenu(); } @@ -596,7 +567,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _app.lock(true); return true; default: - if (item.getGroupId() == R.id.action_filter_group) { + /*if (item.getGroupId() == R.id.action_filter_group) { item.setChecked(true); String group = null; @@ -604,7 +575,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene group = item.getTitle().toString(); } setGroupFilter(group); - } + }*/ if (item.getGroupId() == R.id.action_sort_category) { item.setChecked(true); @@ -811,7 +782,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene for (VaultEntry entry : _selectedEntries) { if (entry.getGroup() != null) { if (!_vault.getGroups().contains(entry.getGroup())) { - updateGroupFilterMenu(); + _entryListView.setGroups(_vault.getGroups()); } } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java index 564fa7e7..52f2f386 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java @@ -25,6 +25,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeSet; import java.util.UUID; public class EntryAdapter extends RecyclerView.Adapter implements ItemTouchHelperAdapter { @@ -40,7 +41,7 @@ public class EntryAdapter extends RecyclerView.Adapter implements I private boolean _tapToReveal; private int _tapToRevealTime; private boolean _copyOnTap; - private String _groupFilter; + private List _groupFilter; private SortCategory _sortCategory; private ViewMode _viewMode; private String _searchFilter; @@ -56,6 +57,7 @@ public class EntryAdapter extends RecyclerView.Adapter implements I _entries = new ArrayList<>(); _shownEntries = new ArrayList<>(); _selectedEntries = new ArrayList<>(); + _groupFilter = new ArrayList<>(); _holders = new ArrayList<>(); _dimHandler = new Handler(); _view = view; @@ -207,12 +209,12 @@ public class EntryAdapter extends RecyclerView.Adapter implements I String issuer = entry.getIssuer().toLowerCase(); String name = entry.getName().toLowerCase(); - if (_groupFilter != null) { - if (group == null && _groupFilter.equals(_view.getContext().getString(R.string.filter_ungrouped))) { + if (!_groupFilter.isEmpty()) { + if (group == null && _groupFilter.contains(_view.getContext().getString(R.string.filter_ungrouped))) { return false; } - if (group == null || !group.equals(_groupFilter)) { + if (group == null || !_groupFilter.contains(group)) { return true; } } @@ -234,11 +236,16 @@ public class EntryAdapter extends RecyclerView.Adapter implements I } } - public void setGroupFilter(String group, boolean apply) { - if (_groupFilter != null && _groupFilter.equals(group)) { + public void setGroupFilter(List groups, boolean apply) { + if(groups == null) { + groups = new ArrayList<>(); + } + + if (_groupFilter.equals(groups)) { return; } - _groupFilter = group; + + _groupFilter = groups; if (apply) { updateShownEntries(); checkPeriodUniformity(); @@ -286,6 +293,10 @@ public class EntryAdapter extends RecyclerView.Adapter implements I _viewMode = viewMode; } + public void setGroups(TreeSet groups) { + _view.setGroups(groups); + } + @Override public void onItemDismiss(int position) { @@ -294,7 +305,7 @@ public class EntryAdapter extends RecyclerView.Adapter implements I @Override public void onItemDrop(int position) { // moving entries is not allowed when a filter is applied - if (_groupFilter != null) { + if (!_groupFilter.isEmpty()) { return; } @@ -304,7 +315,7 @@ public class EntryAdapter extends RecyclerView.Adapter implements I @Override public void onItemMove(int firstPosition, int secondPosition) { // moving entries is not allowed when a filter is applied - if (_groupFilter != null) { + if (!_groupFilter.isEmpty()) { return; } @@ -591,7 +602,7 @@ public class EntryAdapter extends RecyclerView.Adapter implements I } public boolean isDragAndDropAllowed() { - return _sortCategory == SortCategory.CUSTOM && _groupFilter == null && _searchFilter == null; + return _sortCategory == SortCategory.CUSTOM && _groupFilter.isEmpty() && _searchFilter == null; } public boolean isPeriodUniform() { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java index 4a362712..64cfa96f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java @@ -2,6 +2,7 @@ package com.beemdevelopment.aegis.ui.views; import android.annotation.SuppressLint; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Rect; import android.os.Bundle; import android.view.LayoutInflater; @@ -10,6 +11,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; +import android.widget.Button; import android.widget.LinearLayout; import androidx.annotation.NonNull; @@ -28,6 +30,7 @@ import com.beemdevelopment.aegis.helpers.MetricsHelper; import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback; import com.beemdevelopment.aegis.helpers.UiRefresher; import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.ui.Dialogs; import com.beemdevelopment.aegis.vault.VaultEntry; import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; @@ -35,11 +38,17 @@ import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.util.ViewPreloadSizeProvider; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.chip.Chip; +import com.google.android.material.chip.ChipGroup; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.TreeSet; import java.util.UUID; +import java.util.stream.Collectors; public class EntryListView extends Fragment implements EntryAdapter.Listener { private EntryAdapter _adapter; @@ -53,7 +62,10 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { private TotpProgressBar _progressBar; private boolean _showProgress; private ViewMode _viewMode; + private TreeSet _groups; private LinearLayout _emptyStateView; + private Chip _groupChip; + private List _groupFilter; private UiRefresher _refresher; @@ -74,6 +86,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_entry_list_view, container, false); _progressBar = view.findViewById(R.id.progressBar); + _groupChip = view.findViewById(R.id.chip_group); // set up the recycler view _recyclerView = view.findViewById(R.id.rvKeyProfiles); @@ -129,7 +142,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { super.onDestroyView(); } - public void setGroupFilter(String group, boolean apply) { + public void setGroupFilter(List group, boolean apply) { _adapter.setGroupFilter(group, apply); _touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed()); @@ -339,11 +352,67 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { _recyclerView.scheduleLayoutAnimation(); } + private void initializeGroupChip() { + if (_groups.isEmpty()) { + _groupChip.setVisibility(View.GONE); + return; + } + + View view = getLayoutInflater().inflate(R.layout.dialog_select_groups, null); + BottomSheetDialog dialog = new BottomSheetDialog(getContext()); + dialog.setContentView(view); + + ColorStateList colorStateList = ContextCompat.getColorStateList(getContext(), R.color.bg_chip_text_color); + + ChipGroup chipGroup = view.findViewById(R.id.groupChipGroup); + String groupChipText = _groupChip.getText().toString(); + chipGroup.removeAllViews(); + for (String group : _groups) { + Chip chip = new Chip(getContext()); + chip.setText(group); + chip.setCheckable(true); + chip.setCheckedIconVisible(false); + chip.setChipBackgroundColorResource(R.color.bg_chip_color); + chip.setTextColor(colorStateList); + chip.setOnCheckedChangeListener((group1, checkedId) -> { + List groupFilter = chipGroup.getCheckedChipIds().stream() + .map(i -> ((Chip) view.findViewById(i)).getText().toString()) + .collect(Collectors.toList()); + _groupFilter = groupFilter; + setGroupFilter(groupFilter, true); + + if (_groupFilter.isEmpty()) { + _groupChip.setText(groupChipText); + } else { + _groupChip.setText(String.format("%s (%d)", getString(R.string.groups), _groupFilter.size())); + } + }); + + chipGroup.addView(chip); + } + + Button clearButton = view.findViewById(R.id.btnClear); + clearButton.setOnClickListener(v -> { + chipGroup.clearCheck(); + setGroupFilter(null, true); + dialog.dismiss(); + }); + + _groupChip.setOnClickListener(v -> { + Dialogs.showSecureDialog(dialog); + }); + } + private void setShowProgress(boolean showProgress) { _showProgress = showProgress; updateDividerDecoration(); } + public void setGroups(TreeSet groups) { + _groups = groups; + initializeGroupChip(); + } + private void updateDividerDecoration() { if (_dividerDecoration != null) { _recyclerView.removeItemDecoration(_dividerDecoration); @@ -394,10 +463,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { @Override public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { - if (parent.getChildAdapterPosition(view) == 0) { - // the first item should also have a top margin - outRect.top = _height; - } outRect.bottom = _height; } } diff --git a/app/src/main/res/color/bg_chip_color.xml b/app/src/main/res/color/bg_chip_color.xml new file mode 100644 index 00000000..49f20732 --- /dev/null +++ b/app/src/main/res/color/bg_chip_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/bg_chip_text_color.xml b/app/src/main/res/color/bg_chip_text_color.xml new file mode 100644 index 00000000..3ff71a3e --- /dev/null +++ b/app/src/main/res/color/bg_chip_text_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/rounded_dialog.xml b/app/src/main/res/drawable-v24/rounded_dialog.xml new file mode 100644 index 00000000..bd708710 --- /dev/null +++ b/app/src/main/res/drawable-v24/rounded_dialog.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/drag_handle.xml b/app/src/main/res/drawable/drag_handle.xml new file mode 100644 index 00000000..2a8df473 --- /dev/null +++ b/app/src/main/res/drawable/drag_handle.xml @@ -0,0 +1,13 @@ + + + + + + + + + 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 index 2fa2dc86..997895c9 100644 --- a/app/src/main/res/drawable/ic_baseline_filter_list_24dp.xml +++ b/app/src/main/res/drawable/ic_baseline_filter_list_24dp.xml @@ -6,4 +6,5 @@ + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6dd30af9..9c8ee93b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -38,6 +38,13 @@ android:layout_marginStart="5dp" /> + + + + + android:orientation="vertical"> + + + + + + + + + + + + + + +