list: switch to header divider

Switch to item decorations to manage header dividers.

This is much more reliable than encoding it in-data. Only cost comes in
that it forces me to backport yet another component since I still can't
update MDC after over half a year.
This commit is contained in:
Alexander Capehart 2023-01-17 14:30:49 -07:00
parent fb93b82b2b
commit 9e1f6af21e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
20 changed files with 597 additions and 91 deletions

View file

@ -83,9 +83,10 @@ import java.util.Map;
* window-like. For BottomSheetDialog use {@link BottomSheetDialog#setTitle(int)}, and for * window-like. For BottomSheetDialog use {@link BottomSheetDialog#setTitle(int)}, and for
* BottomSheetDialogFragment use {@link ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}. * BottomSheetDialogFragment use {@link ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}.
* *
* Modified at several points by Alexander Capehart to work around miscellaneous issues. * Modified at several points by Alexander Capehart backport miscellaneous fixes not currently
* obtainable in the currently used MDC library.
*/ */
public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { public class BackportBottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
/** Listener for monitoring events about bottom sheets. */ /** Listener for monitoring events about bottom sheets. */
public abstract static class BottomSheetCallback { public abstract static class BottomSheetCallback {
@ -318,9 +319,9 @@ public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Be
private int expandHalfwayActionId = View.NO_ID; private int expandHalfwayActionId = View.NO_ID;
public NeoBottomSheetBehavior() {} public BackportBottomSheetBehavior() {}
public NeoBottomSheetBehavior(@NonNull Context context, @Nullable AttributeSet attrs) { public BackportBottomSheetBehavior(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs); super(context, attrs);
peekHeightGestureInsetBuffer = peekHeightGestureInsetBuffer =
@ -1980,7 +1981,7 @@ public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Be
skipCollapsed = source.readInt() == 1; skipCollapsed = source.readInt() == 1;
} }
public SavedState(Parcelable superState, @NonNull NeoBottomSheetBehavior<?> behavior) { public SavedState(Parcelable superState, @NonNull BackportBottomSheetBehavior<?> behavior) {
super(superState); super(superState);
this.state = behavior.state; this.state = behavior.state;
this.peekHeight = behavior.peekHeight; this.peekHeight = behavior.peekHeight;
@ -1990,12 +1991,12 @@ public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Be
} }
/** /**
* This constructor does not respect flags: {@link NeoBottomSheetBehavior#SAVE_PEEK_HEIGHT}, {@link * This constructor does not respect flags: {@link BackportBottomSheetBehavior#SAVE_PEEK_HEIGHT}, {@link
* NeoBottomSheetBehavior#SAVE_FIT_TO_CONTENTS}, {@link NeoBottomSheetBehavior#SAVE_HIDEABLE}, {@link * BackportBottomSheetBehavior#SAVE_FIT_TO_CONTENTS}, {@link BackportBottomSheetBehavior#SAVE_HIDEABLE}, {@link
* NeoBottomSheetBehavior#SAVE_SKIP_COLLAPSED}. It is as if {@link NeoBottomSheetBehavior#SAVE_NONE} * BackportBottomSheetBehavior#SAVE_SKIP_COLLAPSED}. It is as if {@link BackportBottomSheetBehavior#SAVE_NONE}
* were set. * were set.
* *
* @deprecated Use {@link #SavedState(Parcelable, NeoBottomSheetBehavior)} instead. * @deprecated Use {@link #SavedState(Parcelable, BackportBottomSheetBehavior)} instead.
*/ */
@Deprecated @Deprecated
public SavedState(Parcelable superstate, @State int state) { public SavedState(Parcelable superstate, @State int state) {
@ -2036,24 +2037,24 @@ public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Be
} }
/** /**
* A utility function to get the {@link NeoBottomSheetBehavior} associated with the {@code view}. * A utility function to get the {@link BackportBottomSheetBehavior} associated with the {@code view}.
* *
* @param view The {@link View} with {@link NeoBottomSheetBehavior}. * @param view The {@link View} with {@link BackportBottomSheetBehavior}.
* @return The {@link NeoBottomSheetBehavior} associated with the {@code view}. * @return The {@link BackportBottomSheetBehavior} associated with the {@code view}.
*/ */
@NonNull @NonNull
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <V extends View> NeoBottomSheetBehavior<V> from(@NonNull V view) { public static <V extends View> BackportBottomSheetBehavior<V> from(@NonNull V view) {
ViewGroup.LayoutParams params = view.getLayoutParams(); ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) { if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
} }
CoordinatorLayout.Behavior<?> behavior = CoordinatorLayout.Behavior<?> behavior =
((CoordinatorLayout.LayoutParams) params).getBehavior(); ((CoordinatorLayout.LayoutParams) params).getBehavior();
if (!(behavior instanceof NeoBottomSheetBehavior)) { if (!(behavior instanceof BackportBottomSheetBehavior)) {
throw new IllegalArgumentException("The view is not associated with BottomSheetBehavior"); throw new IllegalArgumentException("The view is not associated with BottomSheetBehavior");
} }
return (NeoBottomSheetBehavior<V>) behavior; return (BackportBottomSheetBehavior<V>) behavior;
} }
/** /**
@ -2200,3 +2201,4 @@ public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Be
}; };
} }
} }

View file

@ -0,0 +1,409 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.material.divider;
import com.google.android.material.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.resources.MaterialResources;
/**
* MaterialDividerItemDecoration is a {@link RecyclerView.ItemDecoration}, similar to a {@link
* androidx.recyclerview.widget.DividerItemDecoration}, that can be used as a divider between items of
* a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and {@link #VERTICAL}
* orientations.
*
* <pre>
* dividerItemDecoration = new MaterialDividerItemDecoration(recyclerView.getContext(),
* layoutManager.getOrientation());
* recyclerView.addItemDecoration(dividerItemDecoration);
* </pre>
*/
public class BackportMaterialDividerItemDecoration extends ItemDecoration {
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_MaterialDivider;
@NonNull private Drawable dividerDrawable;
private int thickness;
@ColorInt private int color;
private int orientation;
private int insetStart;
private int insetEnd;
private boolean lastItemDecorated;
private final Rect tempRect = new Rect();
public BackportMaterialDividerItemDecoration(@NonNull Context context, int orientation) {
this(context, null, orientation);
}
public BackportMaterialDividerItemDecoration(
@NonNull Context context, @Nullable AttributeSet attrs, int orientation) {
this(context, attrs, R.attr.materialDividerStyle, orientation);
}
public BackportMaterialDividerItemDecoration(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int orientation) {
TypedArray attributes =
ThemeEnforcement.obtainStyledAttributes(
context, attrs, R.styleable.MaterialDivider, defStyleAttr, DEF_STYLE_RES);
color =
MaterialResources.getColorStateList(
context, attributes, R.styleable.MaterialDivider_dividerColor)
.getDefaultColor();
thickness =
attributes.getDimensionPixelSize(
R.styleable.MaterialDivider_dividerThickness,
context.getResources().getDimensionPixelSize(R.dimen.material_divider_thickness));
insetStart =
attributes.getDimensionPixelOffset(R.styleable.MaterialDivider_dividerInsetStart, 0);
insetEnd = attributes.getDimensionPixelOffset(R.styleable.MaterialDivider_dividerInsetEnd, 0);
lastItemDecorated =
attributes.getBoolean(R.styleable.MaterialDivider_lastItemDecorated, true);
attributes.recycle();
dividerDrawable = new ShapeDrawable();
setDividerColor(color);
setOrientation(orientation);
}
/**
* Sets the orientation for this divider. This should be called if {@link
* RecyclerView.LayoutManager} changes orientation.
*
* <p>A {@link #HORIZONTAL} orientation will draw a vertical divider, and a {@link #VERTICAL}
* orientation a horizontal divider.
*
* @param orientation The orientation of the {@link RecyclerView} this divider is associated with:
* {@link #HORIZONTAL} or {@link #VERTICAL}
*/
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException(
"Invalid orientation: " + orientation + ". It should be either HORIZONTAL or VERTICAL");
}
this.orientation = orientation;
}
public int getOrientation() {
return orientation;
}
/**
* Sets the thickness of the divider.
*
* @param thickness The thickness value to be set.
* @see #getDividerThickness()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerThickness
*/
public void setDividerThickness(@Px int thickness) {
this.thickness = thickness;
}
/**
* Sets the thickness of the divider.
*
* @param thicknessId The id of the thickness dimension resource to be set.
* @see #getDividerThickness()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerThickness
*/
public void setDividerThicknessResource(@NonNull Context context, @DimenRes int thicknessId) {
setDividerThickness(context.getResources().getDimensionPixelSize(thicknessId));
}
/**
* Returns the thickness set on the divider.
*
* @see #setDividerThickness(int)
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerThickness
*/
@Px
public int getDividerThickness() {
return thickness;
}
/**
* Sets the color of the divider.
*
* @param color The color to be set.
* @see #getDividerColor()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerColor
*/
public void setDividerColor(@ColorInt int color) {
this.color = color;
dividerDrawable = DrawableCompat.wrap(dividerDrawable);
DrawableCompat.setTint(dividerDrawable, color);
}
/**
* Sets the color of the divider.
*
* @param colorId The id of the color resource to be set.
* @see #getDividerColor()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerColor
*/
public void setDividerColorResource(@NonNull Context context, @ColorRes int colorId) {
setDividerColor(ContextCompat.getColor(context, colorId));
}
/**
* Returns the divider color.
*
* @see #setDividerColor(int)
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerColor
*/
@ColorInt
public int getDividerColor() {
return color;
}
/**
* Sets the start inset of the divider.
*
* @param insetStart The start inset to be set.
* @see #getDividerInsetStart()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerInsetStart
*/
public void setDividerInsetStart(@Px int insetStart) {
this.insetStart = insetStart;
}
/**
* Sets the start inset of the divider.
*
* @param insetStartId The id of the inset dimension resource to be set.
* @see #getDividerInsetStart()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerInsetStart
*/
public void setDividerInsetStartResource(@NonNull Context context, @DimenRes int insetStartId) {
setDividerInsetStart(context.getResources().getDimensionPixelOffset(insetStartId));
}
/**
* Returns the divider's start inset.
*
* @see #setDividerInsetStart(int)
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerInsetStart
*/
@Px
public int getDividerInsetStart() {
return insetStart;
}
/**
* Sets the end inset of the divider.
*
* @param insetEnd The end inset to be set.
* @see #getDividerInsetEnd()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerInsetEnd
*/
public void setDividerInsetEnd(@Px int insetEnd) {
this.insetEnd = insetEnd;
}
/**
* Sets the end inset of the divider.
*
* @param insetEndId The id of the inset dimension resource to be set.
* @see #getDividerInsetEnd()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerInsetEnd
*/
public void setDividerInsetEndResource(@NonNull Context context, @DimenRes int insetEndId) {
setDividerInsetEnd(context.getResources().getDimensionPixelOffset(insetEndId));
}
/**
* Returns the divider's end inset.
*
* @see #setDividerInsetEnd(int)
* @attr ref com.google.android.material.R.styleable#MaterialDivider_dividerInsetEnd
*/
@Px
public int getDividerInsetEnd() {
return insetEnd;
}
/**
* Sets whether the class should draw a divider after the last item of a {@link RecyclerView}.
*
* @param lastItemDecorated whether there's a divider after the last item of a recycler view.
* @see #isLastItemDecorated()
* @attr ref com.google.android.material.R.styleable#MaterialDivider_lastItemDecorated
*/
public void setLastItemDecorated(boolean lastItemDecorated) {
this.lastItemDecorated = lastItemDecorated;
}
/**
* Whether there's a divider after the last item of a {@link RecyclerView}.
*
* @see #setLastItemDecorated(boolean)
* @attr ref com.google.android.material.R.styleable#MaterialDivider_shouldDecorateLastItem
*/
public boolean isLastItemDecorated() {
return lastItemDecorated;
}
@Override
public void onDraw(
@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (parent.getLayoutManager() == null) {
return;
}
if (orientation == VERTICAL) {
drawForVerticalOrientation(canvas, parent);
} else {
drawForHorizontalOrientation(canvas, parent);
}
}
/**
* Draws a divider for the vertical orientation of the recycler view. The divider itself will be
* horizontal.
*/
private void drawForVerticalOrientation(@NonNull Canvas canvas, @NonNull RecyclerView parent) {
canvas.save();
int left;
int right;
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(
left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}
boolean isRtl = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL;
left += isRtl ? insetEnd : insetStart;
right -= isRtl ? insetStart : insetEnd;
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
if (shouldDrawDivider(parent, child)) {
parent.getDecoratedBoundsWithMargins(child, tempRect);
// Take into consideration any translationY added to the view.
int bottom = tempRect.bottom + Math.round(child.getTranslationY());
int top = bottom - thickness;
dividerDrawable.setBounds(left, top, right, bottom);
dividerDrawable.draw(canvas);
}
}
canvas.restore();
}
/**
* Draws a divider for the horizontal orientation of the recycler view. The divider itself will be
* vertical.
*/
private void drawForHorizontalOrientation(@NonNull Canvas canvas, @NonNull RecyclerView parent) {
canvas.save();
int top;
int bottom;
if (parent.getClipToPadding()) {
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(
parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom);
} else {
top = 0;
bottom = parent.getHeight();
}
top += insetStart;
bottom -= insetEnd;
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
if (shouldDrawDivider(parent, child)) {
parent.getDecoratedBoundsWithMargins(child, tempRect);
// Take into consideration any translationX added to the view.
int right = tempRect.right + Math.round(child.getTranslationX());
int left = right - thickness;
dividerDrawable.setBounds(left, top, right, bottom);
dividerDrawable.draw(canvas);
}
}
canvas.restore();
}
@Override
public void getItemOffsets(
@NonNull Rect outRect,
@NonNull View view,
@NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
outRect.set(0, 0, 0, 0);
// Only add offset if there's a divider displayed.
if (shouldDrawDivider(parent, view)) {
if (orientation == VERTICAL) {
outRect.bottom = thickness;
} else {
outRect.right = thickness;
}
}
}
private boolean shouldDrawDivider(@NonNull RecyclerView parent, @NonNull View child) {
int position = parent.getChildAdapterPosition(child);
RecyclerView.Adapter<?> adapter = parent.getAdapter();
boolean isLastItem = adapter != null && position == adapter.getItemCount() - 1;
return position != RecyclerView.NO_POSITION
&& (!isLastItem || lastItemDecorated)
&& shouldDrawDivider(position, adapter);
}
/**
* Whether a divider should be drawn below the current item that is being drawn.
*
* <p>Note: if lasItemDecorated is false, the divider below the last item will never be drawn even
* if this method returns true.
*
* @param position the position of the current item being drawn.
* @param adapter the {@link RecyclerView.Adapter} associated with the item being drawn.
*/
protected boolean shouldDrawDivider(int position, @Nullable RecyclerView.Adapter<?> adapter) {
return true;
}
}

View file

@ -30,7 +30,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import com.google.android.material.bottomsheet.BackportBottomSheetBehavior
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import kotlin.math.max import kotlin.math.max
@ -101,10 +101,10 @@ class MainFragment :
val playbackSheetBehavior = val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
unlikelyToBeNull(binding.handleWrapper).setOnClickListener { unlikelyToBeNull(binding.handleWrapper).setOnClickListener {
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED && if (playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_EXPANDED &&
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) { queueSheetBehavior.state == BackportBottomSheetBehavior.STATE_COLLAPSED) {
// Playback sheet is expanded and queue sheet is collapsed, we can expand it. // Playback sheet is expanded and queue sheet is collapsed, we can expand it.
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED queueSheetBehavior.state = BackportBottomSheetBehavior.STATE_EXPANDED
} }
} }
} else { } else {
@ -183,7 +183,7 @@ class MainFragment :
// Playback sheet intercepts queue sheet touch events, prevent that from // Playback sheet intercepts queue sheet touch events, prevent that from
// occurring by disabling dragging whenever the queue sheet is expanded. // occurring by disabling dragging whenever the queue sheet is expanded.
playbackSheetBehavior.isDraggable = playbackSheetBehavior.isDraggable =
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED queueSheetBehavior.state == BackportBottomSheetBehavior.STATE_COLLAPSED
} }
} else { } else {
// No queue sheet, fade normally based on the playback sheet // No queue sheet, fade normally based on the playback sheet
@ -317,9 +317,9 @@ class MainFragment :
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) { if (playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_COLLAPSED) {
// Playback sheet is not expanded and not hidden, we can expand it. // Playback sheet is not expanded and not hidden, we can expand it.
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED playbackSheetBehavior.state = BackportBottomSheetBehavior.STATE_EXPANDED
} }
} }
@ -327,12 +327,12 @@ class MainFragment :
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) { if (playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_EXPANDED) {
// Make sure the queue is also collapsed here. // Make sure the queue is also collapsed here.
val queueSheetBehavior = val queueSheetBehavior =
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED playbackSheetBehavior.state = BackportBottomSheetBehavior.STATE_COLLAPSED
queueSheetBehavior?.state = NeoBottomSheetBehavior.STATE_COLLAPSED queueSheetBehavior?.state = BackportBottomSheetBehavior.STATE_COLLAPSED
} }
} }
@ -340,7 +340,7 @@ class MainFragment :
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_HIDDEN) { if (playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_HIDDEN) {
val queueSheetBehavior = val queueSheetBehavior =
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
// Queue sheet behavior is either collapsed or expanded, no hiding needed // Queue sheet behavior is either collapsed or expanded, no hiding needed
@ -348,7 +348,7 @@ class MainFragment :
playbackSheetBehavior.apply { playbackSheetBehavior.apply {
// Make sure the view is draggable, at least until the draw checks kick in. // Make sure the view is draggable, at least until the draw checks kick in.
isDraggable = true isDraggable = true
state = NeoBottomSheetBehavior.STATE_COLLAPSED state = BackportBottomSheetBehavior.STATE_COLLAPSED
} }
} }
} }
@ -357,19 +357,19 @@ class MainFragment :
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
if (playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN) { if (playbackSheetBehavior.state != BackportBottomSheetBehavior.STATE_HIDDEN) {
val queueSheetBehavior = val queueSheetBehavior =
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
// Make both bottom sheets non-draggable so the user can't halt the hiding event. // Make both bottom sheets non-draggable so the user can't halt the hiding event.
queueSheetBehavior?.apply { queueSheetBehavior?.apply {
isDraggable = false isDraggable = false
state = NeoBottomSheetBehavior.STATE_COLLAPSED state = BackportBottomSheetBehavior.STATE_COLLAPSED
} }
playbackSheetBehavior.apply { playbackSheetBehavior.apply {
isDraggable = false isDraggable = false
state = NeoBottomSheetBehavior.STATE_HIDDEN state = BackportBottomSheetBehavior.STATE_HIDDEN
} }
} }
} }
@ -388,16 +388,16 @@ class MainFragment :
// If expanded, collapse the queue sheet first. // If expanded, collapse the queue sheet first.
if (queueSheetBehavior != null && if (queueSheetBehavior != null &&
queueSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED && queueSheetBehavior.state != BackportBottomSheetBehavior.STATE_COLLAPSED &&
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) { playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_EXPANDED) {
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED queueSheetBehavior.state = BackportBottomSheetBehavior.STATE_COLLAPSED
return return
} }
// If expanded, collapse the playback sheet next. // If expanded, collapse the playback sheet next.
if (playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED && if (playbackSheetBehavior.state != BackportBottomSheetBehavior.STATE_COLLAPSED &&
playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN) { playbackSheetBehavior.state != BackportBottomSheetBehavior.STATE_HIDDEN) {
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED playbackSheetBehavior.state = BackportBottomSheetBehavior.STATE_COLLAPSED
return return
} }
@ -428,8 +428,8 @@ class MainFragment :
val exploreNavController = binding.exploreNavHost.findNavController() val exploreNavController = binding.exploreNavHost.findNavController()
isEnabled = isEnabled =
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED || queueSheetBehavior?.state == BackportBottomSheetBehavior.STATE_EXPANDED ||
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED || playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_EXPANDED ||
selectionModel.selected.value.isNotEmpty() || selectionModel.selected.value.isNotEmpty() ||
exploreNavController.currentDestination?.id != exploreNavController.currentDestination?.id !=
exploreNavController.graph.startDestinationId exploreNavController.graph.startDestinationId

View file

@ -27,4 +27,4 @@ interface Item
* @param titleRes The string resource used for the header's title. * @param titleRes The string resource used for the header's title.
* @param withDivider Whether to show a divider on the item. * @param withDivider Whether to show a divider on the item.
*/ */
data class Header(@StringRes val titleRes: Int, val withDivider: Boolean = true) : Item data class Header(@StringRes val titleRes: Int) : Item

View file

@ -45,6 +45,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
// Auxio's non-dialog RecyclerViews never change their size based on adapter contents, // Auxio's non-dialog RecyclerViews never change their size based on adapter contents,
// so we can enable fixed-size optimizations. // so we can enable fixed-size optimizations.
setHasFixedSize(true) setHasFixedSize(true)
addItemDecoration(HeaderItemDecoration(context))
} }
final override fun setHasFixedSize(hasFixedSize: Boolean) { final override fun setHasFixedSize(hasFixedSize: Boolean) {
@ -52,6 +53,10 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
super.setHasFixedSize(hasFixedSize) super.setHasFixedSize(hasFixedSize)
} }
final override fun addItemDecoration(decor: ItemDecoration) {
super.addItemDecoration(decor)
}
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
// Update the RecyclerView's padding such that the bottom insets are applied // Update the RecyclerView's padding such that the bottom insets are applied
// while still preserving bottom padding. // while still preserving bottom padding.
@ -78,6 +83,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
} }
} }
/** A [RecyclerView.Adapter]-specific hook to control divider decoration visibility. */
/** An [RecyclerView.Adapter]-specific hook to [GridLayoutManager.SpanSizeLookup]. */ /** An [RecyclerView.Adapter]-specific hook to [GridLayoutManager.SpanSizeLookup]. */
interface SpanSizeLookup { interface SpanSizeLookup {
/** /**

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.list.recycler
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.BackportMaterialDividerItemDecoration
import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.Header
class HeaderItemDecoration
@JvmOverloads
constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = R.attr.materialDividerStyle,
orientation: Int = LinearLayoutManager.VERTICAL
) : BackportMaterialDividerItemDecoration(context, attributeSet, defStyleAttr, orientation) {
override fun shouldDrawDivider(position: Int, adapter: RecyclerView.Adapter<*>?) =
try {
(adapter as DiffAdapter<*, *, *>).getItem(position + 1) is Header
} catch (e: ClassCastException) {
false
} catch (e: IndexOutOfBoundsException) {
false
}
}

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.list.recycler package org.oxycblt.auxio.list.recycler
import android.view.View import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -31,6 +30,7 @@ import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
/** /**
* A [RecyclerView.ViewHolder] that displays a [Song]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [Song]. Use [from] to create an instance.
@ -249,8 +249,8 @@ class HeaderViewHolder private constructor(private val binding: ItemHeaderBindin
* @param header The new [Header] to bind. * @param header The new [Header] to bind.
*/ */
fun bind(header: Header) { fun bind(header: Header) {
logD(binding.context.getString(header.titleRes))
binding.title.text = binding.context.getString(header.titleRes) binding.title.text = binding.context.getString(header.titleRes)
binding.headerDivider.isVisible = header.withDivider
} }
companion object { companion object {

View file

@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.list.* import org.oxycblt.auxio.list.*
import org.oxycblt.auxio.list.recycler.* import org.oxycblt.auxio.list.recycler.*
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.util.logD
/** /**
* An adapter that displays search results. * An adapter that displays search results.
@ -54,6 +55,7 @@ class SearchAdapter(private val listener: SelectableListListener<Music>) :
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
logD(position)
when (val item = getItem(position)) { when (val item = getItem(position)) {
is Song -> (holder as SongViewHolder).bind(item, listener) is Song -> (holder as SongViewHolder).bind(item, listener)
is Album -> (holder as AlbumViewHolder).bind(item, listener) is Album -> (holder as AlbumViewHolder).bind(item, listener)
@ -65,7 +67,16 @@ class SearchAdapter(private val listener: SelectableListListener<Music>) :
override fun isItemFullWidth(position: Int) = getItem(position) is Header override fun isItemFullWidth(position: Int) = getItem(position) is Header
/**
* Make sure that the top header has a correctly configured divider visibility. This would
* normally be automatically done by the differ, but that results in a strange animation.
*/
fun pokeDividers() {
notifyItemChanged(0, PAYLOAD_UPDATE_DIVIDER)
}
private companion object { private companion object {
val PAYLOAD_UPDATE_DIVIDER = 102249124
/** A comparator that can be used with DiffUtil. */ /** A comparator that can be used with DiffUtil. */
val DIFF_CALLBACK = val DIFF_CALLBACK =
object : SimpleItemCallback<Item>() { object : SimpleItemCallback<Item>() {

View file

@ -159,6 +159,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
// the query actually changes instead of once every re-creation event, but sadly // the query actually changes instead of once every re-creation event, but sadly
// that doesn't seem possible. // that doesn't seem possible.
binding.searchRecycler.scrollToPosition(0) binding.searchRecycler.scrollToPosition(0)
searchAdapter.pokeDividers()
} }
} }

View file

@ -110,21 +110,21 @@ class SearchViewModel(application: Application) :
if (filterMode == null || filterMode == MusicMode.ARTISTS) { if (filterMode == null || filterMode == MusicMode.ARTISTS) {
library.artists.searchListImpl(query)?.let { library.artists.searchListImpl(query)?.let {
results.add(Header(R.string.lbl_artists, withDivider = results.isNotEmpty())) results.add(Header(R.string.lbl_artists))
results.addAll(sort.artists(it)) results.addAll(sort.artists(it))
} }
} }
if (filterMode == null || filterMode == MusicMode.ALBUMS) { if (filterMode == null || filterMode == MusicMode.ALBUMS) {
library.albums.searchListImpl(query)?.let { library.albums.searchListImpl(query)?.let {
results.add(Header(R.string.lbl_albums, withDivider = results.isNotEmpty())) results.add(Header(R.string.lbl_albums))
results.addAll(sort.albums(it)) results.addAll(sort.albums(it))
} }
} }
if (filterMode == null || filterMode == MusicMode.GENRES) { if (filterMode == null || filterMode == MusicMode.GENRES) {
library.genres.searchListImpl(query)?.let { library.genres.searchListImpl(query)?.let {
results.add(Header(R.string.lbl_genres, withDivider = results.isNotEmpty())) results.add(Header(R.string.lbl_genres))
results.addAll(sort.genres(it)) results.addAll(sort.genres(it))
} }
} }
@ -133,7 +133,7 @@ class SearchViewModel(application: Application) :
library.songs library.songs
.searchListImpl(query) { q, song -> song.path.name.contains(q) } .searchListImpl(query) { q, song -> song.path.name.contains(q) }
?.let { ?.let {
results.add(Header(R.string.lbl_songs, withDivider = results.isNotEmpty())) results.add(Header(R.string.lbl_songs))
results.addAll(sort.songs(it)) results.addAll(sort.songs(it))
} }
} }

View file

@ -87,6 +87,7 @@ abstract class BasePreferenceFragment(@XmlRes private val screen: Int) :
) = ) =
super.onCreateRecyclerView(inflater, parent, savedInstanceState).apply { super.onCreateRecyclerView(inflater, parent, savedInstanceState).apply {
clipToPadding = false clipToPadding = false
addItemDecoration(PreferenceHeaderItemDecoration(context))
setOnApplyWindowInsetsListener { _, insets -> setOnApplyWindowInsetsListener { _, insets ->
updatePadding(bottom = insets.systemBarInsetsCompat.bottom) updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
insets insets

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.settings.ui
import android.content.Context
import android.util.AttributeSet
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceGroupAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.BackportMaterialDividerItemDecoration
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.logD
class PreferenceHeaderItemDecoration
@JvmOverloads
constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = R.attr.materialDividerStyle,
orientation: Int = LinearLayoutManager.VERTICAL
) : BackportMaterialDividerItemDecoration(context, attributeSet, defStyleAttr, orientation) {
override fun shouldDrawDivider(position: Int, adapter: RecyclerView.Adapter<*>?) =
try {
logD(position)
(adapter as PreferenceGroupAdapter).getItem(position) is PreferenceCategory
} catch (e: ClassCastException) {
false
} catch (e: IndexOutOfBoundsException) {
false
}
}

View file

@ -24,7 +24,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowInsets import android.view.WindowInsets
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import com.google.android.material.bottomsheet.BackportBottomSheetBehavior
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.getDimen import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.systemGestureInsetsCompat import org.oxycblt.auxio.util.systemGestureInsetsCompat
@ -37,7 +37,7 @@ import org.oxycblt.auxio.util.systemGestureInsetsCompat
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class BaseBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) : abstract class BaseBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
NeoBottomSheetBehavior<V>(context, attributeSet) { BackportBottomSheetBehavior<V>(context, attributeSet) {
private var initalized = false private var initalized = false
init { init {

View file

@ -22,7 +22,7 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import com.google.android.material.bottomsheet.BackportBottomSheetBehavior
import kotlin.math.abs import kotlin.math.abs
import org.oxycblt.auxio.util.coordinatorLayoutBehavior import org.oxycblt.auxio.util.coordinatorLayoutBehavior
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
@ -42,7 +42,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
private var setup = false private var setup = false
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean { override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
if (dependency.coordinatorLayoutBehavior is NeoBottomSheetBehavior) { if (dependency.coordinatorLayoutBehavior is BackportBottomSheetBehavior) {
dep = dependency dep = dependency
return true return true
} }
@ -55,7 +55,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
child: V, child: V,
dependency: View dependency: View
): Boolean { ): Boolean {
val behavior = dependency.coordinatorLayoutBehavior as NeoBottomSheetBehavior val behavior = dependency.coordinatorLayoutBehavior as BackportBottomSheetBehavior
val consumed = behavior.calculateConsumedByBar() val consumed = behavior.calculateConsumedByBar()
if (consumed == Int.MIN_VALUE) { if (consumed == Int.MIN_VALUE) {
return false return false
@ -87,7 +87,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
heightUsed: Int heightUsed: Int
): Boolean { ): Boolean {
val dep = dep ?: return false val dep = dep ?: return false
val behavior = dep.coordinatorLayoutBehavior as NeoBottomSheetBehavior val behavior = dep.coordinatorLayoutBehavior as BackportBottomSheetBehavior
val consumed = behavior.calculateConsumedByBar() val consumed = behavior.calculateConsumedByBar()
if (consumed == Int.MIN_VALUE) { if (consumed == Int.MIN_VALUE) {
return false return false
@ -106,7 +106,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
child.setOnApplyWindowInsetsListener { _, insets -> child.setOnApplyWindowInsetsListener { _, insets ->
lastInsets = insets lastInsets = insets
val dep = dep ?: return@setOnApplyWindowInsetsListener insets val dep = dep ?: return@setOnApplyWindowInsetsListener insets
val behavior = dep.coordinatorLayoutBehavior as NeoBottomSheetBehavior val behavior = dep.coordinatorLayoutBehavior as BackportBottomSheetBehavior
val consumed = behavior.calculateConsumedByBar() val consumed = behavior.calculateConsumedByBar()
if (consumed == Int.MIN_VALUE) { if (consumed == Int.MIN_VALUE) {
return@setOnApplyWindowInsetsListener insets return@setOnApplyWindowInsetsListener insets
@ -138,7 +138,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
child.layout(0, 0, child.measuredWidth, child.measuredHeight) child.layout(0, 0, child.measuredWidth, child.measuredHeight)
} }
private fun NeoBottomSheetBehavior<*>.calculateConsumedByBar(): Int { private fun BackportBottomSheetBehavior<*>.calculateConsumedByBar(): Int {
val offset = calculateSlideOffset() val offset = calculateSlideOffset()
if (offset == Float.MIN_VALUE || peekHeight < 0) { if (offset == Float.MIN_VALUE || peekHeight < 0) {
return Int.MIN_VALUE return Int.MIN_VALUE

View file

@ -11,6 +11,12 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.divider.MaterialDivider
android:id="@+id/dirs_mode_header_divider"
app:layout_constraintTop_toTopOf="parent"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<TextView <TextView
style="@style/Widget.Auxio.TextView.Header" style="@style/Widget.Auxio.TextView.Header"
android:id="@+id/dirs_mode_header" android:id="@+id/dirs_mode_header"
@ -19,13 +25,8 @@
android:paddingStart="@dimen/spacing_large" android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large" android:paddingEnd="@dimen/spacing_large"
android:text="@string/set_dirs_mode" android:text="@string/set_dirs_mode"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toBottomOf="@+id/dirs_mode_header_divider" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/dirs_mode_header_divider"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@+id/dirs_mode_header"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButtonToggleGroup <com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/folder_mode_group" android:id="@+id/folder_mode_group"
@ -35,7 +36,7 @@
android:layout_marginTop="@dimen/spacing_medium" android:layout_marginTop="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_large" android:layout_marginEnd="@dimen/spacing_large"
android:gravity="center" android:gravity="center"
app:layout_constraintTop_toBottomOf="@+id/dirs_mode_header_divider" app:layout_constraintTop_toBottomOf="@+id/dirs_mode_header"
app:checkedButton="@+id/dirs_mode_exclude" app:checkedButton="@+id/dirs_mode_exclude"
app:selectionRequired="true" app:selectionRequired="true"
app:singleSelection="true"> app:singleSelection="true">
@ -70,6 +71,13 @@
app:layout_constraintTop_toBottomOf="@+id/folder_mode_group" app:layout_constraintTop_toBottomOf="@+id/folder_mode_group"
tools:text="Mode description" /> tools:text="Mode description" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/dirs_list_header_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_medium"
app:layout_constraintTop_toBottomOf="@+id/dirs_mode_desc"/>
<TextView <TextView
android:id="@+id/dirs_list_header" android:id="@+id/dirs_list_header"
style="@style/Widget.Auxio.TextView.Header" style="@style/Widget.Auxio.TextView.Header"
@ -78,9 +86,7 @@
android:paddingStart="@dimen/spacing_large" android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large" android:paddingEnd="@dimen/spacing_large"
android:text="@string/set_dirs_list" android:text="@string/set_dirs_list"
android:layout_marginTop="@dimen/spacing_medium" app:layout_constraintTop_toBottomOf="@+id/dirs_list_header_divider" />
app:layout_constraintTop_toBottomOf="@+id/dirs_mode_desc"
app:layout_constraintBottom_toTopOf="@+id/dirs_list_header_divider" />
<Button <Button
style="@style/Widget.Auxio.Button.Icon.Small" style="@style/Widget.Auxio.Button.Icon.Small"
@ -90,14 +96,10 @@
app:icon="@drawable/ic_add_24" app:icon="@drawable/ic_add_24"
android:contentDescription="@string/lbl_add" android:contentDescription="@string/lbl_add"
android:layout_marginEnd="@dimen/spacing_mid_large" android:layout_marginEnd="@dimen/spacing_mid_large"
app:layout_constraintBottom_toTopOf="@+id/dirs_list_header_divider" app:layout_constraintTop_toBottomOf="@+id/dirs_list_header_divider"
app:layout_constraintEnd_toEndOf="@+id/dirs_list_header" /> app:layout_constraintEnd_toEndOf="@+id/dirs_list_header" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/dirs_list_header_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/dirs_recycler"/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/dirs_recycler" android:id="@+id/dirs_recycler"
@ -109,7 +111,7 @@
android:clipToPadding="false" android:clipToPadding="false"
android:overScrollMode="never" android:overScrollMode="never"
android:paddingTop="@dimen/spacing_small" android:paddingTop="@dimen/spacing_small"
app:layout_constraintTop_toTopOf="@+id/dirs_list_header_divider" app:layout_constraintTop_toBottomOf="@+id/dirs_list_header"
tools:listitem="@layout/item_music_dir" /> tools:listitem="@layout/item_music_dir" />
<TextView <TextView

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/title"
style="@style/Widget.Auxio.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Songs" />

View file

@ -6,12 +6,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.divider.MaterialDivider
android:id="@+id/header_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView <TextView
android:id="@android:id/title" android:id="@android:id/title"
style="@style/Widget.Auxio.TextView.Header" style="@style/Widget.Auxio.TextView.Header"

View file

@ -2,8 +2,7 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/set_audio"> app:title="@string/set_audio">
<PreferenceCategory android:title="@string/set_playback" <PreferenceCategory android:title="@string/set_playback">
app:layout="@layout/item_bare_header">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="@string/set_key_headset_autoplay" app:key="@string/set_key_headset_autoplay"

View file

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/set_content"> app:title="@string/set_content">
<PreferenceCategory app:title="@string/set_music" <PreferenceCategory app:title="@string/set_music">
app:layout="@layout/item_bare_header">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="false" app:defaultValue="false"

View file

@ -3,8 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/set_personalize"> app:title="@string/set_personalize">
<PreferenceCategory app:title="@string/set_display" <PreferenceCategory app:title="@string/set_display">
app:layout="@layout/item_bare_header">
<org.oxycblt.auxio.settings.ui.WrappedDialogPreference <org.oxycblt.auxio.settings.ui.WrappedDialogPreference
app:key="@string/set_key_home_tabs" app:key="@string/set_key_home_tabs"