33import static androidx .recyclerview .widget .RecyclerView .NO_POSITION ;
44
55import android .annotation .SuppressLint ;
6+ import android .content .res .TypedArray ;
67import android .graphics .Rect ;
78import android .graphics .drawable .Drawable ;
89import android .os .Bundle ;
10+ import android .util .TypedValue ;
911import android .view .LayoutInflater ;
1012import android .view .MotionEvent ;
1113import android .view .View ;
1416import android .widget .Button ;
1517import android .widget .LinearLayout ;
1618
19+ import androidx .annotation .AttrRes ;
1720import androidx .annotation .NonNull ;
1821import androidx .annotation .Nullable ;
22+ import androidx .annotation .StyleRes ;
1923import androidx .fragment .app .Fragment ;
20- import androidx .recyclerview .widget .DividerItemDecoration ;
2124import androidx .recyclerview .widget .GridLayoutManager ;
2225import androidx .recyclerview .widget .ItemTouchHelper ;
2326import androidx .recyclerview .widget .LinearLayoutManager ;
5053import com .google .android .material .card .MaterialCardView ;
5154import com .google .android .material .chip .Chip ;
5255import com .google .android .material .chip .ChipGroup ;
53- import com .google .android .material .divider .MaterialDividerItemDecoration ;
5456import com .google .android .material .shape .CornerFamily ;
5557import com .google .android .material .shape .ShapeAppearanceModel ;
5658import com .google .common .base .Strings ;
@@ -70,8 +72,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
7072 private ItemTouchHelper _touchHelper ;
7173
7274 private RecyclerView _recyclerView ;
73- private RecyclerView .ItemDecoration _verticalDividerDecoration ;
74- private RecyclerView .ItemDecoration _horizontalDividerDecoration ;
75+ private RecyclerView .ItemDecoration _itemDecoration ;
7576 private ViewPreloadSizeProvider <VaultEntry > _preloadSizeProvider ;
7677 private TotpProgressBar _progressBar ;
7778 private boolean _showProgress ;
@@ -255,7 +256,7 @@ public void setViewMode(ViewMode mode) {
255256 _touchCallback .setDragFlags (ItemTouchHelper .UP | ItemTouchHelper .DOWN );
256257 }
257258
258- ((GridLayoutManager )_recyclerView .getLayoutManager ()).setSpanCount (mode .getColumnSpan ());
259+ ((GridLayoutManager )_recyclerView .getLayoutManager ()).setSpanCount (mode .getSpanCount ());
259260 }
260261
261262 public void startDrag (RecyclerView .ViewHolder viewHolder ) {
@@ -591,28 +592,18 @@ private Set<UUID> cleanGroupFilter(Set<UUID> groupFilter) {
591592 }
592593
593594 private void updateDividerDecoration () {
594- if (_verticalDividerDecoration != null ) {
595- _recyclerView .removeItemDecoration (_verticalDividerDecoration );
595+ if (_itemDecoration != null ) {
596+ _recyclerView .removeItemDecoration (_itemDecoration );
596597 }
597598
598- if (_horizontalDividerDecoration != null ) {
599- _recyclerView .removeItemDecoration (_horizontalDividerDecoration );
600- }
601-
602- float height = _viewMode .getDividerHeight ();
603- float width = _viewMode .getDividerWidth ();
604- if (_showProgress && height == 0 ) {
605- _verticalDividerDecoration = new CompactDividerDecoration ();
599+ float offset = _viewMode .getItemOffset ();
600+ if (_viewMode == ViewMode .TILES ) {
601+ _itemDecoration = new TileSpaceItemDecoration (offset );
606602 } else {
607- _verticalDividerDecoration = new VerticalSpaceItemDecoration (height );
603+ _itemDecoration = new VerticalSpaceItemDecoration (offset );
608604 }
609605
610- if (width != 0 ) {
611- _horizontalDividerDecoration = new TileSpaceItemDecoration (width , height );
612- _recyclerView .addItemDecoration (_horizontalDividerDecoration );
613- } else {
614- _recyclerView .addItemDecoration (_verticalDividerDecoration );
615- }
606+ _recyclerView .addItemDecoration (_itemDecoration );
616607 }
617608
618609 private void updateEmptyState () {
@@ -642,61 +633,18 @@ public interface Listener {
642633 void onEntryListTouch ();
643634 }
644635
645- private void decorateFavoriteEntries (@ NonNull View view , @ NonNull RecyclerView parent ) {
646- int adapterPosition = parent .getChildAdapterPosition (view );
647- int entryIndex = _adapter .translateEntryPosToIndex (adapterPosition );
648- int totalFavorites = _adapter .getShownFavoritesCount ();
649-
650- if (entryIndex < totalFavorites ) {
651- ShapeAppearanceModel model = ((MaterialCardView )view ).getShapeAppearanceModel ();
652- ShapeAppearanceModel .Builder builder = model .toBuilder ();
653- if ((entryIndex == 0 && totalFavorites > 1 ) || (entryIndex < (totalFavorites - 1 ))) {
654- builder .setBottomLeftCorner (CornerFamily .ROUNDED , 0 );
655- builder .setBottomRightCorner (CornerFamily .ROUNDED , 0 );
656- }
657- if (entryIndex > 0 ) {
658- builder .setTopLeftCorner (CornerFamily .ROUNDED , 0 );
659- builder .setTopRightCorner (CornerFamily .ROUNDED , 0 );
660- }
661-
662- ((MaterialCardView )view ).setShapeAppearanceModel (builder .build ());
663- }
664- }
665-
666- private class CompactDividerDecoration extends MaterialDividerItemDecoration {
667- public CompactDividerDecoration () {
668- super (requireContext (), DividerItemDecoration .VERTICAL );
669- setDividerColorResource (requireContext (), android .R .color .transparent );
670- setLastItemDecorated (false );
671- setDividerThickness (MetricsHelper .convertDpToPixels (requireContext (), 0.5f ));
672- }
673-
674- @ Override
675- public void getItemOffsets (@ NonNull Rect outRect , @ NonNull View view , @ NonNull RecyclerView parent , @ NonNull RecyclerView .State state ) {
676- if (_adapter .isPositionErrorCard (parent .getChildAdapterPosition (view ))) {
677- outRect .top = MetricsHelper .convertDpToPixels (requireContext (), 4 );
678- return ;
679- }
680-
681- if (_adapter .isPositionFooter (parent .getChildAdapterPosition (view ))) {
682- int pixels = MetricsHelper .convertDpToPixels (requireContext (), 20 );
683- outRect .top = pixels ;
684- outRect .bottom = pixels ;
685- return ;
686- }
636+ private class VerticalSpaceItemDecoration extends RecyclerView .ItemDecoration {
637+ private final int _offset ;
638+ private final ShapeAppearanceModel _defaultShapeModel ;
687639
688- decorateFavoriteEntries (view , parent );
640+ private VerticalSpaceItemDecoration (float offset ) {
641+ _offset = MetricsHelper .convertDpToPixels (requireContext (), offset );
689642
690- super .getItemOffsets (outRect , view , parent , state );
691- }
692- }
643+ int shapeAppearanceId = getStyledAttrs (R .style .Widget_Aegis_EntryCardView ,
644+ com .google .android .material .R .attr .shapeAppearance );
693645
694- private class VerticalSpaceItemDecoration extends RecyclerView .ItemDecoration {
695- private final int _height ;
696-
697- private VerticalSpaceItemDecoration (float dp ) {
698- // convert dp to pixels
699- _height = MetricsHelper .convertDpToPixels (requireContext (), dp );
646+ _defaultShapeModel = ShapeAppearanceModel .builder (
647+ requireContext (), shapeAppearanceId , 0 ).build ();
700648 }
701649
702650 @ Override
@@ -707,57 +655,80 @@ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull R
707655 }
708656
709657 // The error card and the footer always have a top and bottom margin
710- if (_adapter .isPositionErrorCard (adapterPosition )
711- || _adapter .isPositionFooter (adapterPosition )) {
712- outRect .top = _height ;
713- outRect .bottom = _height ;
658+ if (_adapter .isPositionErrorCard (adapterPosition )) {
659+ outRect .top = _viewMode == ViewMode .COMPACT ? _offset * 4 : _offset ;
660+ outRect .bottom = _offset ;
661+ return ;
662+ }
663+ if (_adapter .isPositionFooter (adapterPosition )) {
664+ outRect .top = _offset * 2 ;
665+ outRect .bottom = _offset ;
714666 return ;
715667 }
716668
717669 int entryIndex = _adapter .translateEntryPosToIndex (adapterPosition );
718670 // The first entry should have a top margin, but only if the group chip is not shown and the error card is not shown
719671 if (entryIndex == 0 && (_groups == null || _groups .isEmpty ()) && !_adapter .isErrorCardShown ()) {
720- outRect .top = _height ;
672+ outRect .top = _offset ;
721673 }
722674
723675 // Only non-favorite entries have a bottom margin, except for the final favorite entry
724676 int totalFavorites = _adapter .getShownFavoritesCount ();
725677 if (totalFavorites == 0
726678 || (entryIndex < _adapter .getShownEntriesCount () && !_adapter .getEntryAtPos (adapterPosition ).isFavorite ())
727679 || totalFavorites == entryIndex + 1 ) {
728- outRect .bottom = _height ;
680+ outRect .bottom = _offset ;
729681 }
730682
731- if (totalFavorites > 0 ) {
732- // If this entry is the last favorite entry in the list, it should always have
733- // a bottom margin, regardless of the view mode
734- if (entryIndex == totalFavorites - 1 ) {
735- outRect .bottom = _height ;
736- }
683+ // The last entry should never have a bottom margin
684+ if (_adapter .getShownEntriesCount () == entryIndex + 1 ) {
685+ outRect .bottom = 0 ;
686+ }
737687
738- // If this is the first non-favorite entry, it should have a top margin
739- if (entryIndex == totalFavorites ) {
740- outRect .top = _height ;
741- }
688+ decorateFavoriteEntries ((MaterialCardView ) view , parent );
689+ }
742690
743- decorateFavoriteEntries (view , parent );
691+ private void decorateFavoriteEntries (@ NonNull MaterialCardView view , @ NonNull RecyclerView parent ) {
692+ int adapterPosition = parent .getChildAdapterPosition (view );
693+ int entryIndex = _adapter .translateEntryPosToIndex (adapterPosition );
694+ int totalFavorites = _adapter .getShownFavoritesCount ();
695+
696+ ShapeAppearanceModel .Builder builder = _defaultShapeModel .toBuilder ();
697+ if (entryIndex < totalFavorites ) {
698+ if ((entryIndex == 0 && totalFavorites > 1 ) || (entryIndex < (totalFavorites - 1 ))) {
699+ builder .setBottomLeftCorner (CornerFamily .ROUNDED , 0 );
700+ builder .setBottomRightCorner (CornerFamily .ROUNDED , 0 );
701+ }
702+ if (entryIndex > 0 ) {
703+ builder .setTopLeftCorner (CornerFamily .ROUNDED , 0 );
704+ builder .setTopRightCorner (CornerFamily .ROUNDED , 0 );
705+ }
744706 }
745707
746- // The last entry should never have a bottom margin
747- if (_adapter .getShownEntriesCount () == entryIndex + 1 ) {
748- outRect .bottom = 0 ;
708+ view .setShapeAppearanceModel (builder .build ());
709+ view .setClipToOutline (true );
710+ }
711+
712+ private int getStyledAttrs (@ StyleRes int styleId , @ AttrRes int attrId ) {
713+ TypedArray cardAttrs = null ;
714+ try {
715+ cardAttrs = requireContext ().obtainStyledAttributes (styleId , new int []{attrId });
716+ TypedValue value = new TypedValue ();
717+ cardAttrs .getValue (0 , value );
718+ return value .data ;
719+ } finally {
720+ if (cardAttrs != null ) {
721+ cardAttrs .recycle ();
722+ }
749723 }
750724 }
751725 }
752726
753727 private class TileSpaceItemDecoration extends RecyclerView .ItemDecoration {
754- private final int _width ;
755- private final int _height ;
728+ private final int _offset ;
756729
757- private TileSpaceItemDecoration (float width , float height ) {
758- // convert dp to pixels
759- _width = MetricsHelper .convertDpToPixels (requireContext (), width );
760- _height = MetricsHelper .convertDpToPixels (requireContext (), height );
730+ private TileSpaceItemDecoration (float offset ) {
731+ _offset = MetricsHelper .convertDpToPixels (requireContext (), offset );
761732 }
762733
763734 @ Override
@@ -767,10 +738,21 @@ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull R
767738 return ;
768739 }
769740
770- outRect .left = _width ;
771- outRect .right = _width ;
772- outRect .top = _height ;
773- outRect .bottom = _height ;
741+ outRect .left = _offset ;
742+ outRect .right = _offset ;
743+ outRect .top = _offset ;
744+ outRect .bottom = _offset ;
745+
746+ if (_adapter .isPositionErrorCard (adapterPosition )
747+ || (isInFirstEntryRow (adapterPosition ) && !_adapter .isErrorCardShown ())
748+ || _adapter .isPositionFooter (adapterPosition )) {
749+ outRect .top *= 2 ;
750+ }
751+ }
752+
753+ private boolean isInFirstEntryRow (int pos ) {
754+ int index = _adapter .translateEntryPosToIndex (pos );
755+ return index >= 0 && index < _viewMode .getSpanCount ();
774756 }
775757 }
776758
0 commit comments