diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java index cc921be732..903efeda7e 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java @@ -177,12 +177,10 @@ private void changeSort(@IdRes int resId) { } private void changeGroupFilter(String text) { - onView(withId(R.id.chip_group)).perform(click()); if (text == null) { - onView(withId(R.id.btnClear)).perform(click()); + onView(allOf(withText(R.string.all), isDescendantOfA(withId(R.id.groupChipGroup)))).perform(click()); } else { - onView(withText(text)).perform(click()); - onView(isRoot()).perform(pressBack()); + onView(allOf(withText(text), isDescendantOfA(withId(R.id.groupChipGroup)))).perform(click()); } } 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 82680e4f3e..733cf12608 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -50,19 +50,26 @@ import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment; import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment; import com.beemdevelopment.aegis.ui.models.ErrorCardInfo; +import com.beemdevelopment.aegis.ui.models.VaultGroupModel; import com.beemdevelopment.aegis.ui.tasks.QrDecodeTask; import com.beemdevelopment.aegis.ui.views.EntryListView; import com.beemdevelopment.aegis.util.TimeUtils; +import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultFile; +import com.beemdevelopment.aegis.vault.VaultGroup; import com.beemdevelopment.aegis.vault.VaultRepository; import com.beemdevelopment.aegis.vault.VaultRepositoryException; import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.chip.Chip; +import com.google.android.material.chip.ChipGroup; +import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.common.base.Strings; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; @@ -91,6 +98,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private SearchView _searchView; private EntryListView _entryListView; + private Collection _groups; + private ChipGroup _groupChip; + private Set _groupFilter; + private Set _prefGroupFilter; + private FabScrollHelper _fabScrollHelper; private ActionMode _actionMode; @@ -196,7 +208,7 @@ protected void onCreate(Bundle savedInstanceState) { _entryListView.setViewMode(_prefs.getCurrentViewMode()); _entryListView.setCopyBehavior(_prefs.getCopyBehavior()); _entryListView.setSearchBehaviorMask(_prefs.getSearchBehaviorMask()); - _entryListView.setPrefGroupFilter(_prefs.getGroupFilter()); + _prefGroupFilter = _prefs.getGroupFilter(); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(v -> { @@ -220,10 +232,135 @@ protected void onCreate(Bundle savedInstanceState) { Dialogs.showSecureDialog(dialog); }); + _groupChip = findViewById(R.id.groupChipGroup); _fabScrollHelper = new FabScrollHelper(fab); _selectedEntries = new ArrayList<>(); } + public void setGroups(Collection groups) { + _groups = groups; + _groupChip.setVisibility(_groups.isEmpty() ? View.GONE : View.VISIBLE); + + if (_prefGroupFilter != null) { + Set groupFilter = cleanGroupFilter(_prefGroupFilter); + _prefGroupFilter = null; + if (!groupFilter.isEmpty()) { + _groupFilter = groupFilter; + _entryListView.setGroupFilter(groupFilter, false); + } + } else if (_groupFilter != null) { + Set groupFilter = cleanGroupFilter(_groupFilter); + if (!_groupFilter.equals(groupFilter)) { + _groupFilter = groupFilter; + _entryListView.setGroupFilter(groupFilter, true); + } + } + + _entryListView.setGroups(groups); + initializeGroups(); + } + + private void initializeGroups() { + _groupChip.removeAllViews(); + + addChipTo(_groupChip, new VaultGroupModel(getString(R.string.all))); + + for (VaultGroup group : _groups) { + addChipTo(_groupChip, new VaultGroupModel(group)); + } + + addSaveChip(_groupChip); + } + + private Set cleanGroupFilter(Set groupFilter) { + Set groupUuids = _groups.stream().map(UUIDMap.Value::getUUID).collect(Collectors.toSet()); + + return groupFilter.stream() + .filter(g -> g == null || groupUuids.contains(g)) + .collect(Collectors.toSet()); + } + + private void addChipTo(ChipGroup chipGroup, VaultGroupModel group) { + Chip chip = (Chip) getLayoutInflater().inflate(R.layout.chip_group_filter, null, false); + chip.setText(group.getName()); + chip.setCheckable(true); + chip.setCheckedIconVisible(false); + chip.setChecked(_groupFilter != null && _groupFilter.contains(group.getUUID())); + + if (group.isPlaceholder()) { + chip.setId(0); + chip.setChecked(_groupFilter == null); + chip.setOnClickListener(v -> { + Chip checkedChip = (Chip) chipGroup.getChildAt(0); + chipGroup.clearCheck(); + checkedChip.setChecked(true); + }); + + chipGroup.addView(chip); + return; + } + + + chip.setOnCheckedChangeListener((group1, checkedId) -> { + setSaveChipVisibility(true); + Set groupFilter = getGroupFilter(chipGroup); + + Chip allGroupsChip = (Chip) chipGroup.getChildAt(0); + if (groupFilter.isEmpty()) { + allGroupsChip.setChecked(true); + } else { + allGroupsChip.setChecked(false); + } + + _groupFilter = groupFilter; + _entryListView.setGroupFilter(groupFilter, true); + }); + + chip.setTag(group); + chipGroup.addView(chip); + } + + private void addSaveChip(ChipGroup chipGroup) { + Chip chip = (Chip) getLayoutInflater().inflate(R.layout.chip_group_filter, null, false); + + chip.setText(getString(R.string.save)); + chip.setVisibility(View.GONE); + chip.setChipStrokeWidth(0); + chip.setCheckable(false); + chip.setChipBackgroundColorResource(android.R.color.transparent); + chip.setTextColor(MaterialColors.getColor(chip.getRootView(), com.google.android.material.R.attr.colorSecondary)); + chip.setClickable(true); + chip.setCheckedIconVisible(false); + chip.setOnClickListener(v -> { + Set groupFilter = getGroupFilter(chipGroup); + onSaveGroupFilter(cleanGroupFilter(groupFilter)); + setSaveChipVisibility(false); + }); + + chipGroup.addView(chip); + } + + private void setSaveChipVisibility(boolean visible) { + Chip saveChip = (Chip) _groupChip.getChildAt(_groupChip.getChildCount() - 1); + saveChip.setChecked(false); + saveChip.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + private static Set getGroupFilter(ChipGroup chipGroup) { + return chipGroup.getCheckedChipIds().stream() + .map(i -> { + Chip chip = chipGroup.findViewById(i); + if (chip.getTag() != null) { + VaultGroupModel group = (VaultGroupModel) chip.getTag(); + return group.getUUID(); + } + + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + @Override protected void onDestroy() { _entryListView.setListener(null); @@ -677,7 +814,7 @@ protected void onStart() { startAuthActivity(false); } else if (_loaded) { // update the list of groups in the entry list view so that the chip gets updated - _entryListView.setGroups(_vaultManager.getVault().getUsedGroups()); + setGroups(_vaultManager.getVault().getUsedGroups()); // update the usage counts in case they are edited outside of the EntryListView _entryListView.setUsageCounts(_prefs.getUsageCounts()); @@ -717,7 +854,7 @@ public boolean onCreateOptionsMenu(Menu menu) { updateLockIcon(); if (_loaded) { - _entryListView.setGroups(_vaultManager.getVault().getUsedGroups()); + setGroups(_vaultManager.getVault().getUsedGroups()); updateSortCategoryMenu(); } @@ -836,7 +973,7 @@ private void collapseSearchView() { private void loadEntries() { if (!_loaded) { - _entryListView.setGroups(_vaultManager.getVault().getUsedGroups()); + setGroups(_vaultManager.getVault().getUsedGroups()); _entryListView.setUsageCounts(_prefs.getUsageCounts()); _entryListView.setLastUsedTimestamps(_prefs.getLastUsedTimestamps()); _entryListView.addEntries(_vaultManager.getVault().getEntries()); 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 6bd4fb6847..967ea002ba 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 @@ -13,15 +13,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.LayoutAnimationController; -import android.widget.Button; import android.widget.LinearLayout; import androidx.annotation.AttrRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.widget.NestedScrollView; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.ItemTouchHelper; @@ -39,10 +36,8 @@ import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback; import com.beemdevelopment.aegis.helpers.UiRefresher; import com.beemdevelopment.aegis.otp.TotpInfo; -import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.ui.models.ErrorCardInfo; -import com.beemdevelopment.aegis.ui.models.VaultGroupModel; import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultGroup; @@ -51,11 +46,7 @@ import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; import com.bumptech.glide.util.ViewPreloadSizeProvider; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.card.MaterialCardView; -import com.google.android.material.chip.Chip; -import com.google.android.material.chip.ChipGroup; import com.google.android.material.shape.CornerFamily; import com.google.android.material.shape.ShapeAppearanceModel; import com.google.common.base.Strings; @@ -83,7 +74,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { private ViewMode _viewMode; private Collection _groups; private LinearLayout _emptyStateView; - private Chip _groupChip; private Set _groupFilter; private Set _prefGroupFilter; @@ -106,8 +96,6 @@ public void onDestroy() { 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); - initializeGroupChip(); // set up the recycler view _recyclerView = view.findViewById(R.id.rvKeyProfiles); @@ -202,12 +190,16 @@ public void onDestroyView() { super.onDestroyView(); } + public void setGroups(Collection groups) { + _groups = groups; + _adapter.setGroups(groups); + } + public void setGroupFilter(Set groups, boolean animate) { _groupFilter = groups; _adapter.setGroupFilter(groups); _touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed()); updateEmptyState(); - updateGroupChip(); if (animate) { runEntriesAnimation(); @@ -384,10 +376,6 @@ public void onListChange() { } } - public void setPrefGroupFilter(Set groupFilter) { - _prefGroupFilter = groupFilter; - } - public void setCodeGroupSize(Preferences.CodeGrouping codeGrouping) { _adapter.setCodeGroupSize(codeGrouping); } @@ -519,116 +507,11 @@ public void runEntriesAnimation() { _recyclerView.scheduleLayoutAnimation(); } - private void addChipTo(ChipGroup chipGroup, VaultGroupModel group) { - Chip chip = (Chip) getLayoutInflater().inflate(R.layout.chip_group_filter, null, false); - chip.setText(group.getName()); - chip.setCheckable(true); - chip.setChecked(_groupFilter != null && _groupFilter.contains(group.getUUID())); - chip.setCheckedIconVisible(true); - chip.setOnCheckedChangeListener((group1, checkedId) -> { - Set groupFilter = getGroupFilter(chipGroup); - setGroupFilter(groupFilter, true); - }); - chip.setTag(group); - chipGroup.addView(chip); - } - - private void initializeGroupChip() { - View view = getLayoutInflater().inflate(R.layout.dialog_select_groups, null); - BottomSheetDialog dialog = new BottomSheetDialog(requireContext()); - NestedScrollView scrollView = view.findViewById(R.id.scrollView); - ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) scrollView.getLayoutParams(); - layoutParams.matchConstraintMaxHeight = getResources().getConfiguration().screenHeightDp; - - dialog.getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED); - dialog.getBehavior().setSkipCollapsed(false); - dialog.setContentView(view); - - ChipGroup chipGroup = view.findViewById(R.id.groupChipGroup); - Button clearButton = view.findViewById(R.id.btnClear); - Button saveButton = view.findViewById(R.id.btnSave); - clearButton.setOnClickListener(v -> { - chipGroup.clearCheck(); - Set groupFilter = Collections.emptySet(); - if (_listener != null) { - _listener.onSaveGroupFilter(groupFilter); - } - setGroupFilter(groupFilter, true); - dialog.dismiss(); - }); - - saveButton.setOnClickListener(v -> { - Set groupFilter = getGroupFilter(chipGroup); - if (_listener != null) { - _listener.onSaveGroupFilter(groupFilter); - } - setGroupFilter(groupFilter, true); - dialog.dismiss(); - }); - - _groupChip.setOnClickListener(v -> { - chipGroup.removeAllViews(); - - for (VaultGroup group : _groups) { - addChipTo(chipGroup, new VaultGroupModel(group)); - } - addChipTo(chipGroup, new VaultGroupModel(getString(R.string.no_group))); - - Dialogs.showSecureDialog(dialog); - }); - } - - private static Set getGroupFilter(ChipGroup chipGroup) { - return chipGroup.getCheckedChipIds().stream() - .map(i -> { - Chip chip = chipGroup.findViewById(i); - VaultGroupModel group = (VaultGroupModel) chip.getTag(); - return group.getUUID(); - }) - .collect(Collectors.toSet()); - } - - private void updateGroupChip() { - if (_groupFilter.isEmpty()) { - _groupChip.setText(R.string.groups); - } else { - _groupChip.setText(String.format("%s (%d)", getString(R.string.groups), _groupFilter.size())); - } - } - private void setShowProgress(boolean showProgress) { _showProgress = showProgress; updateDividerDecoration(); } - public void setGroups(Collection groups) { - _groups = groups; - _adapter.setGroups(groups); - _groupChip.setVisibility(_groups.isEmpty() ? View.GONE : View.VISIBLE); - updateDividerDecoration(); - - if (_prefGroupFilter != null) { - Set groupFilter = cleanGroupFilter(_prefGroupFilter); - _prefGroupFilter = null; - if (!groupFilter.isEmpty()) { - setGroupFilter(groupFilter, false); - } - } else if (_groupFilter != null) { - Set groupFilter = cleanGroupFilter(_groupFilter); - if (!_groupFilter.equals(groupFilter)) { - setGroupFilter(groupFilter, true); - } - } - } - - private Set cleanGroupFilter(Set groupFilter) { - Set groupUuids = _groups.stream().map(UUIDMap.Value::getUUID).collect(Collectors.toSet()); - - return groupFilter.stream() - .filter(g -> g == null || groupUuids.contains(g)) - .collect(Collectors.toSet()); - } - private void updateDividerDecoration() { if (_itemDecoration != null) { _recyclerView.removeItemDecoration(_itemDecoration); diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ccc10366dd..104b03e2a2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -15,25 +15,43 @@ android:fitsSystemWindows="true" app:liftOnScroll="true" app:liftOnScrollTargetViewId="@+id/rvKeyProfiles"> + - - + + + + + + + + + + + - - - - diff --git a/app/src/main/res/layout/fragment_entry_list_view.xml b/app/src/main/res/layout/fragment_entry_list_view.xml index bd452d12bf..eaacfb4b61 100644 --- a/app/src/main/res/layout/fragment_entry_list_view.xml +++ b/app/src/main/res/layout/fragment_entry_list_view.xml @@ -14,21 +14,6 @@ android:visibility="gone" android:max="5000"/> - - Restore default icon Discard Save + All Issuer PIN (4–16 digits) PIN (4 digits)