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:
parent
fb93b82b2b
commit
9e1f6af21e
20 changed files with 597 additions and 91 deletions
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>() {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue