Fix various minor inconsistencies in entry list item offsets

This patch addresses the following:
- More consistent offsets between entries in the list, especially in
  relation to the action bar and the error card.
- Consistent correct application of card shapes when switching between
  favoriting and unfavoriting entries.
- Removal of CompactDividerDecoration. We no longer uses dividers, so
  this is no longer needed.
This commit is contained in:
Alexander Bakker 2024-03-16 20:45:02 +01:00
parent 9737c85f86
commit 45ced0de60
2 changed files with 92 additions and 118 deletions

View File

@ -35,11 +35,11 @@ public enum ViewMode {
}
/**
* Retrieves the height (in dp) that the divider between entries should have in this view mode.
* Retrieves the offset (in dp) that should exist between entries in this view mode.
*/
public float getDividerHeight() {
public float getItemOffset() {
if (this == ViewMode.COMPACT) {
return 0;
return 1;
} else if (this == ViewMode.TILES) {
return 4;
}
@ -47,7 +47,7 @@ public enum ViewMode {
return 8;
}
public int getColumnSpan() {
public int getSpanCount() {
if (this == ViewMode.TILES) {
return 2;
}
@ -55,14 +55,6 @@ public enum ViewMode {
return 1;
}
public float getDividerWidth() {
if (this == ViewMode.TILES) {
return 4;
}
return 0;
}
public String getFormattedAccountName(String accountName) {
if (this == ViewMode.TILES) {
return accountName;

View File

@ -3,9 +3,11 @@ package com.beemdevelopment.aegis.ui.views;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.annotation.SuppressLint;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@ -14,10 +16,11 @@ 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.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -50,7 +53,6 @@ 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.divider.MaterialDividerItemDecoration;
import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.ShapeAppearanceModel;
import com.google.common.base.Strings;
@ -70,8 +72,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private ItemTouchHelper _touchHelper;
private RecyclerView _recyclerView;
private RecyclerView.ItemDecoration _verticalDividerDecoration;
private RecyclerView.ItemDecoration _horizontalDividerDecoration;
private RecyclerView.ItemDecoration _itemDecoration;
private ViewPreloadSizeProvider<VaultEntry> _preloadSizeProvider;
private TotpProgressBar _progressBar;
private boolean _showProgress;
@ -255,7 +256,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_touchCallback.setDragFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN);
}
((GridLayoutManager)_recyclerView.getLayoutManager()).setSpanCount(mode.getColumnSpan());
((GridLayoutManager)_recyclerView.getLayoutManager()).setSpanCount(mode.getSpanCount());
}
public void startDrag(RecyclerView.ViewHolder viewHolder) {
@ -591,28 +592,18 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
}
private void updateDividerDecoration() {
if (_verticalDividerDecoration != null) {
_recyclerView.removeItemDecoration(_verticalDividerDecoration);
if (_itemDecoration != null) {
_recyclerView.removeItemDecoration(_itemDecoration);
}
if(_horizontalDividerDecoration != null) {
_recyclerView.removeItemDecoration(_horizontalDividerDecoration);
}
float height = _viewMode.getDividerHeight();
float width = _viewMode.getDividerWidth();
if (_showProgress && height == 0) {
_verticalDividerDecoration = new CompactDividerDecoration();
float offset = _viewMode.getItemOffset();
if (_viewMode == ViewMode.TILES) {
_itemDecoration = new TileSpaceItemDecoration(offset);
} else {
_verticalDividerDecoration = new VerticalSpaceItemDecoration(height);
_itemDecoration = new VerticalSpaceItemDecoration(offset);
}
if (width != 0) {
_horizontalDividerDecoration = new TileSpaceItemDecoration(width, height);
_recyclerView.addItemDecoration(_horizontalDividerDecoration);
} else {
_recyclerView.addItemDecoration(_verticalDividerDecoration);
}
_recyclerView.addItemDecoration(_itemDecoration);
}
private void updateEmptyState() {
@ -642,61 +633,18 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
void onEntryListTouch();
}
private void decorateFavoriteEntries(@NonNull View view, @NonNull RecyclerView parent) {
int adapterPosition = parent.getChildAdapterPosition(view);
int entryIndex = _adapter.translateEntryPosToIndex(adapterPosition);
int totalFavorites = _adapter.getShownFavoritesCount();
if (entryIndex < totalFavorites) {
ShapeAppearanceModel model = ((MaterialCardView)view).getShapeAppearanceModel();
ShapeAppearanceModel.Builder builder = model.toBuilder();
if ((entryIndex == 0 && totalFavorites > 1) || (entryIndex < (totalFavorites - 1))) {
builder.setBottomLeftCorner(CornerFamily.ROUNDED, 0);
builder.setBottomRightCorner(CornerFamily.ROUNDED, 0);
}
if (entryIndex > 0) {
builder.setTopLeftCorner(CornerFamily.ROUNDED, 0);
builder.setTopRightCorner(CornerFamily.ROUNDED, 0);
}
((MaterialCardView)view).setShapeAppearanceModel(builder.build());
}
}
private class CompactDividerDecoration extends MaterialDividerItemDecoration {
public CompactDividerDecoration() {
super(requireContext(), DividerItemDecoration.VERTICAL);
setDividerColorResource(requireContext(), android.R.color.transparent);
setLastItemDecorated(false);
setDividerThickness(MetricsHelper.convertDpToPixels(requireContext(), 0.5f));
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (_adapter.isPositionErrorCard(parent.getChildAdapterPosition(view))) {
outRect.top = MetricsHelper.convertDpToPixels(requireContext(), 4);
return;
}
if (_adapter.isPositionFooter(parent.getChildAdapterPosition(view))) {
int pixels = MetricsHelper.convertDpToPixels(requireContext(), 20);
outRect.top = pixels;
outRect.bottom = pixels;
return;
}
decorateFavoriteEntries(view, parent);
super.getItemOffsets(outRect, view, parent, state);
}
}
private class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int _height;
private final int _offset;
private final ShapeAppearanceModel _defaultShapeModel;
private VerticalSpaceItemDecoration(float dp) {
// convert dp to pixels
_height = MetricsHelper.convertDpToPixels(requireContext(), dp);
private VerticalSpaceItemDecoration(float offset) {
_offset = MetricsHelper.convertDpToPixels(requireContext(), offset);
int shapeAppearanceId = getStyledAttrs(R.style.Widget_Aegis_EntryCardView,
com.google.android.material.R.attr.shapeAppearance);
_defaultShapeModel = ShapeAppearanceModel.builder(
requireContext(), shapeAppearanceId, 0).build();
}
@Override
@ -707,17 +655,21 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
}
// The error card and the footer always have a top and bottom margin
if (_adapter.isPositionErrorCard(adapterPosition)
|| _adapter.isPositionFooter(adapterPosition)) {
outRect.top = _height;
outRect.bottom = _height;
if (_adapter.isPositionErrorCard(adapterPosition)) {
outRect.top = _viewMode == ViewMode.COMPACT ? _offset * 4 : _offset;
outRect.bottom = _offset;
return;
}
if (_adapter.isPositionFooter(adapterPosition)) {
outRect.top = _offset * 2;
outRect.bottom = _offset;
return;
}
int entryIndex = _adapter.translateEntryPosToIndex(adapterPosition);
// The first entry should have a top margin, but only if the group chip is not shown and the error card is not shown
if (entryIndex == 0 && (_groups == null || _groups.isEmpty()) && !_adapter.isErrorCardShown()) {
outRect.top = _height;
outRect.top = _offset;
}
// Only non-favorite entries have a bottom margin, except for the final favorite entry
@ -725,39 +677,58 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
if (totalFavorites == 0
|| (entryIndex < _adapter.getShownEntriesCount() && !_adapter.getEntryAtPos(adapterPosition).isFavorite())
|| totalFavorites == entryIndex + 1) {
outRect.bottom = _height;
}
if (totalFavorites > 0) {
// If this entry is the last favorite entry in the list, it should always have
// a bottom margin, regardless of the view mode
if (entryIndex == totalFavorites - 1) {
outRect.bottom = _height;
}
// If this is the first non-favorite entry, it should have a top margin
if (entryIndex == totalFavorites) {
outRect.top = _height;
}
decorateFavoriteEntries(view, parent);
outRect.bottom = _offset;
}
// The last entry should never have a bottom margin
if (_adapter.getShownEntriesCount() == entryIndex + 1) {
outRect.bottom = 0;
}
decorateFavoriteEntries((MaterialCardView) view, parent);
}
private void decorateFavoriteEntries(@NonNull MaterialCardView view, @NonNull RecyclerView parent) {
int adapterPosition = parent.getChildAdapterPosition(view);
int entryIndex = _adapter.translateEntryPosToIndex(adapterPosition);
int totalFavorites = _adapter.getShownFavoritesCount();
ShapeAppearanceModel.Builder builder = _defaultShapeModel.toBuilder();
if (entryIndex < totalFavorites) {
if ((entryIndex == 0 && totalFavorites > 1) || (entryIndex < (totalFavorites - 1))) {
builder.setBottomLeftCorner(CornerFamily.ROUNDED, 0);
builder.setBottomRightCorner(CornerFamily.ROUNDED, 0);
}
if (entryIndex > 0) {
builder.setTopLeftCorner(CornerFamily.ROUNDED, 0);
builder.setTopRightCorner(CornerFamily.ROUNDED, 0);
}
}
view.setShapeAppearanceModel(builder.build());
view.setClipToOutline(true);
}
private int getStyledAttrs(@StyleRes int styleId, @AttrRes int attrId) {
TypedArray cardAttrs = null;
try {
cardAttrs = requireContext().obtainStyledAttributes(styleId, new int[]{attrId});
TypedValue value = new TypedValue();
cardAttrs.getValue(0, value);
return value.data;
} finally {
if (cardAttrs != null) {
cardAttrs.recycle();
}
}
}
}
private class TileSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int _width;
private final int _height;
private final int _offset;
private TileSpaceItemDecoration(float width, float height) {
// convert dp to pixels
_width = MetricsHelper.convertDpToPixels(requireContext(), width);
_height = MetricsHelper.convertDpToPixels(requireContext(), height);
private TileSpaceItemDecoration(float offset) {
_offset = MetricsHelper.convertDpToPixels(requireContext(), offset);
}
@Override
@ -767,10 +738,21 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
return;
}
outRect.left = _width;
outRect.right = _width;
outRect.top = _height;
outRect.bottom = _height;
outRect.left = _offset;
outRect.right = _offset;
outRect.top = _offset;
outRect.bottom = _offset;
if (_adapter.isPositionErrorCard(adapterPosition)
|| (isInFirstEntryRow(adapterPosition) && !_adapter.isErrorCardShown())
|| _adapter.isPositionFooter(adapterPosition)) {
outRect.top *= 2;
}
}
private boolean isInFirstEntryRow(int pos) {
int index = _adapter.translateEntryPosToIndex(pos);
return index >= 0 && index < _viewMode.getSpanCount();
}
}