home: clean up fast scroller
Clean up the fast scroller implementation to remove dead code and be more coherent in general.
This commit is contained in:
parent
f1bd6b1c8c
commit
90f10f2a84
13 changed files with 377 additions and 431 deletions
|
@ -106,7 +106,6 @@ dependencies {
|
|||
spotless {
|
||||
kotlin {
|
||||
target "src/**/*.kt"
|
||||
|
||||
ktfmt('0.30').dropboxStyle()
|
||||
licenseHeaderFile("NOTICE")
|
||||
}
|
||||
|
|
|
@ -40,8 +40,11 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
/**
|
||||
* The single [AppCompatActivity] for Auxio.
|
||||
*
|
||||
* TODO: Add a new view for crashes with a stack trace TODO: Custom language support TODO: Rework
|
||||
* menus [perhaps add multi-select]
|
||||
* TODO: Add a new view for crashes with a stack trace
|
||||
*
|
||||
* TODO: Custom language support
|
||||
*
|
||||
* TODO: Rework menus [perhaps add multi-select]
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val playbackModel: PlaybackViewModel by viewModels()
|
||||
|
|
|
@ -55,7 +55,9 @@ import org.oxycblt.auxio.util.logTraceOrThrow
|
|||
* respective item.
|
||||
* @author OxygenCobalt
|
||||
*
|
||||
* TODO: Make tabs invisible when there is only one TODO: Add duration and song count sorts
|
||||
* TODO: Make tabs invisible when there is only one
|
||||
*
|
||||
* TODO: Add duration and song count sorts
|
||||
*/
|
||||
class HomeFragment : Fragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.home.fastscroll
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import kotlin.math.sqrt
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.getAttrColorSafe
|
||||
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
||||
|
||||
/**
|
||||
* The custom drawable used as FastScrollRecyclerView's popup background. This is an adaptation from
|
||||
* AndroidFastScroll's MD2 theme.
|
||||
*
|
||||
* Attributions as per the Apache 2.0 license: ORIGINAL AUTHOR: Hai Zhang
|
||||
* [https://github.com/zhanghai] PROJECT: Android Fast Scroll
|
||||
* [https://github.com/zhanghai/AndroidFastScroll] MODIFIER: OxygenCobalt [https://github.com/]
|
||||
*
|
||||
* !!! MODIFICATIONS !!!:
|
||||
* - Use modified Auxio resources instead of AFS resources
|
||||
* - Variable names are no longer prefixed with m
|
||||
* - Made path management compat-friendly
|
||||
* - Converted to kotlin
|
||||
*
|
||||
* @author Hai Zhang, OxygenCobalt
|
||||
*/
|
||||
class FastScrollPopupDrawable(context: Context) : Drawable() {
|
||||
private val paint: Paint =
|
||||
Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = context.getAttrColorSafe(R.attr.colorSecondary)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
private val path = Path()
|
||||
private val matrix = Matrix()
|
||||
|
||||
private val paddingStart = context.getDimenOffsetSafe(R.dimen.spacing_medium)
|
||||
private val paddingEnd = context.getDimenOffsetSafe(R.dimen.popup_padding_end)
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.drawPath(path, paint)
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
updatePath()
|
||||
}
|
||||
|
||||
override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
|
||||
updatePath()
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getOutline(outline: Outline) {
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> outline.setPath(path)
|
||||
|
||||
// Paths don't need to be convex on android Q, but the API was mislabeled and so
|
||||
// we still have to use this method.
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> outline.setConvexPath(path)
|
||||
else ->
|
||||
if (!path.isConvex) {
|
||||
// The outline path must be convex before Q, but we may run into floating point
|
||||
// errors caused by calculations involving sqrt(2) or OEM implementation
|
||||
// differences,
|
||||
// so in this case we just omit the shadow instead of crashing.
|
||||
super.getOutline(outline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPadding(padding: Rect): Boolean {
|
||||
if (isRtl) {
|
||||
padding[paddingEnd, 0, paddingStart] = 0
|
||||
} else {
|
||||
padding[paddingStart, 0, paddingEnd] = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isAutoMirrored(): Boolean = true
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
override fun setAlpha(alpha: Int) {}
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {}
|
||||
|
||||
private fun updatePath() {
|
||||
path.reset()
|
||||
|
||||
var width = bounds.width().toFloat()
|
||||
val height = bounds.height().toFloat()
|
||||
val r = height / 2
|
||||
val sqrt2 = sqrt(2.0).toFloat()
|
||||
|
||||
// Ensure we are convex
|
||||
width = (r + sqrt2 * r).coerceAtLeast(width)
|
||||
pathArcTo(path, r, r, r, 90f, 180f)
|
||||
|
||||
val o1X = width - sqrt2 * r
|
||||
pathArcTo(path, o1X, r, r, -90f, 45f)
|
||||
|
||||
val r2 = r / 5
|
||||
val o2X = width - sqrt2 * r2
|
||||
pathArcTo(path, o2X, r, r2, -45f, 90f)
|
||||
pathArcTo(path, o1X, r, r, 45f, 45f)
|
||||
|
||||
path.close()
|
||||
|
||||
if (isRtl) {
|
||||
matrix.setScale(-1f, 1f, width / 2, 0f)
|
||||
} else {
|
||||
matrix.reset()
|
||||
}
|
||||
|
||||
matrix.postTranslate(bounds.left.toFloat(), bounds.top.toFloat())
|
||||
|
||||
path.transform(matrix)
|
||||
}
|
||||
|
||||
private fun pathArcTo(
|
||||
path: Path,
|
||||
centerX: Float,
|
||||
centerY: Float,
|
||||
radius: Float,
|
||||
startAngle: Float,
|
||||
sweepAngle: Float
|
||||
) {
|
||||
path.arcTo(
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
centerX + radius,
|
||||
centerY + radius,
|
||||
startAngle,
|
||||
sweepAngle,
|
||||
false)
|
||||
}
|
||||
|
||||
private val isRtl: Boolean
|
||||
get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.home.fastscroll
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.getAttrColorSafe
|
||||
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
||||
import org.oxycblt.auxio.util.isRtl
|
||||
|
||||
class FastScrollPopupView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
|
||||
MaterialTextView(context, attrs, defStyleRes) {
|
||||
init {
|
||||
minimumWidth = context.getDimenSizeSafe(R.dimen.fast_scroll_popup_min_width)
|
||||
minimumHeight = context.getDimenSizeSafe(R.dimen.fast_scroll_popup_min_height)
|
||||
|
||||
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
|
||||
setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary))
|
||||
ellipsize = TextUtils.TruncateAt.MIDDLE
|
||||
gravity = Gravity.CENTER
|
||||
includeFontPadding = false
|
||||
|
||||
alpha = 0f
|
||||
elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat()
|
||||
background = FastScrollPopupDrawable(context)
|
||||
}
|
||||
|
||||
private class FastScrollPopupDrawable(context: Context) : Drawable() {
|
||||
private val paint: Paint =
|
||||
Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = context.getAttrColorSafe(R.attr.colorSecondary)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
private val path = Path()
|
||||
private val matrix = Matrix()
|
||||
|
||||
private val paddingStart =
|
||||
context.getDimenOffsetSafe(R.dimen.fast_scroll_popup_padding_start)
|
||||
private val paddingEnd = context.getDimenOffsetSafe(R.dimen.fast_scroll_popup_padding_end)
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.drawPath(path, paint)
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
updatePath()
|
||||
}
|
||||
|
||||
override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
|
||||
updatePath()
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getOutline(outline: Outline) {
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> outline.setPath(path)
|
||||
|
||||
// Paths don't need to be convex on android Q, but the API was mislabeled and so
|
||||
// we still have to use this method.
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> outline.setConvexPath(path)
|
||||
else ->
|
||||
if (!path.isConvex) {
|
||||
// The outline path must be convex before Q, but we may run into floating
|
||||
// point errors caused by calculations involving sqrt(2) or OEM differences,
|
||||
// so in this case we just omit the shadow instead of crashing.
|
||||
super.getOutline(outline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPadding(padding: Rect): Boolean {
|
||||
if (isRtl) {
|
||||
padding[paddingEnd, 0, paddingStart] = 0
|
||||
} else {
|
||||
padding[paddingStart, 0, paddingEnd] = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isAutoMirrored(): Boolean = true
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
override fun setAlpha(alpha: Int) {}
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {}
|
||||
|
||||
private fun updatePath() {
|
||||
val r = bounds.height().toFloat() / 2
|
||||
val w = (r + SQRT2 * r).coerceAtLeast(bounds.width().toFloat())
|
||||
|
||||
path.apply {
|
||||
reset()
|
||||
|
||||
// Draw the left pill shape
|
||||
val o1X = w - SQRT2 * r
|
||||
arcToSafe(r, r, r, 90f, 180f)
|
||||
arcToSafe(o1X, r, r, -90f, 45f)
|
||||
|
||||
// Draw the right arrow shape
|
||||
val point = r / 5
|
||||
val o2X = w - SQRT2 * point
|
||||
arcToSafe(o2X, r, point, -45f, 90f)
|
||||
arcToSafe(o1X, r, r, 45f, 45f)
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
matrix.apply {
|
||||
reset()
|
||||
if (isRtl) setScale(-1f, 1f, w / 2, 0f)
|
||||
postTranslate(bounds.left.toFloat(), bounds.top.toFloat())
|
||||
}
|
||||
|
||||
path.transform(matrix)
|
||||
}
|
||||
|
||||
private fun Path.arcToSafe(
|
||||
centerX: Float,
|
||||
centerY: Float,
|
||||
radius: Float,
|
||||
startAngle: Float,
|
||||
sweepAngle: Float
|
||||
) {
|
||||
path.arcTo(
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
centerX + radius,
|
||||
centerY + radius,
|
||||
startAngle,
|
||||
sweepAngle,
|
||||
false)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SQRT2 = 1.4142135623730950488f
|
||||
}
|
||||
}
|
|
@ -17,11 +17,9 @@
|
|||
|
||||
package org.oxycblt.auxio.home.fastscroll
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
|
@ -30,12 +28,9 @@ import android.view.ViewConfiguration
|
|||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -43,19 +38,21 @@ import kotlin.math.abs
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.ui.EdgeRecyclerView
|
||||
import org.oxycblt.auxio.util.canScroll
|
||||
import org.oxycblt.auxio.util.getAttrColorSafe
|
||||
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
import org.oxycblt.auxio.util.isRtl
|
||||
import org.oxycblt.auxio.util.isUnder
|
||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
|
||||
/**
|
||||
* A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of
|
||||
* Hai Zhang's AndroidFastScroll but slimmed down for Auxio and with a couple of enhancements.
|
||||
*
|
||||
* Attributions as per the Apache 2.0 license: ORIGINAL AUTHOR: Hai Zhang
|
||||
* [https://github.com/zhanghai] PROJECT: Android Fast Scroll
|
||||
* [https://github.com/zhanghai/AndroidFastScroll] MODIFIER: OxygenCobalt [https://github.com/]
|
||||
* Attributions as per the Apache 2.0 license:
|
||||
* - ORIGINAL AUTHOR: Hai Zhang [https://github.com/zhanghai]
|
||||
* - PROJECT: Android Fast Scroll [https://github.com/zhanghai/AndroidFastScroll]
|
||||
* - MODIFIER: OxygenCobalt [https://github.com/oxygencobalt]
|
||||
*
|
||||
* !!! MODIFICATIONS !!!:
|
||||
* - Scroller will no longer show itself on startup or relayouts, which looked unpleasant with
|
||||
|
@ -72,11 +69,90 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
* - Added documentation
|
||||
*
|
||||
* @author Hai Zhang, OxygenCobalt
|
||||
*
|
||||
* TODO: Fix strange touch behavior when the pointer is slightly outside of the view.
|
||||
*
|
||||
* TODO: Really try to make this view less insane.
|
||||
*/
|
||||
class FastScrollRecyclerView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
EdgeRecyclerView(context, attrs, defStyleAttr) {
|
||||
private val minTouchTargetSize =
|
||||
context.getDimenSizeSafe(R.dimen.fast_scroll_thumb_touch_target_size)
|
||||
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
// Thumb
|
||||
private val thumbView =
|
||||
View(context).apply {
|
||||
alpha = 0f
|
||||
background = context.getDrawableSafe(R.drawable.ui_scroll_thumb)
|
||||
this@FastScrollRecyclerView.overlay.add(this)
|
||||
}
|
||||
|
||||
private val thumbWidth = thumbView.background.intrinsicWidth
|
||||
private val thumbHeight = thumbView.background.intrinsicHeight
|
||||
private val thumbPadding = Rect(0, 0, 0, 0)
|
||||
private var thumbOffset = 0
|
||||
|
||||
private var showingThumb = false
|
||||
private val hideThumbRunnable = Runnable {
|
||||
if (!dragging) {
|
||||
hideScrollbar()
|
||||
}
|
||||
}
|
||||
|
||||
// Popup
|
||||
private val popupView =
|
||||
FastScrollPopupView(context).apply {
|
||||
layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
.apply {
|
||||
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
||||
marginEnd = context.getDimenOffsetSafe(R.dimen.spacing_small)
|
||||
}
|
||||
|
||||
this@FastScrollRecyclerView.overlay.add(this)
|
||||
}
|
||||
|
||||
private var showingPopup = false
|
||||
|
||||
// Touch events
|
||||
private var downX = 0f
|
||||
private var downY = 0f
|
||||
private var lastY = 0f
|
||||
private var dragStartY = 0f
|
||||
private var dragStartThumbOffset = 0
|
||||
|
||||
private var dragging = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
|
||||
if (value) {
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
}
|
||||
|
||||
thumbView.isPressed = value
|
||||
|
||||
if (field) {
|
||||
removeCallbacks(hideThumbRunnable)
|
||||
showScrollbar()
|
||||
showPopup()
|
||||
} else {
|
||||
postAutoHideScrollbar()
|
||||
hidePopup()
|
||||
}
|
||||
|
||||
onDragListener?.invoke(value)
|
||||
}
|
||||
|
||||
private val childRect = Rect()
|
||||
|
||||
/** Callback to provide a string to be shown on the popup when an item is passed */
|
||||
var popupProvider: ((Int) -> String)? = null
|
||||
|
||||
|
@ -86,83 +162,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
*/
|
||||
var onDragListener: ((Boolean) -> Unit)? = null
|
||||
|
||||
private val minTouchTargetSize: Int = context.getDimenSizeSafe(R.dimen.size_btn_small)
|
||||
private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
// Views for the track, thumb, and popup. Note that the track view is mostly vestigial
|
||||
// and is only for bounds checking.
|
||||
private val trackView: View
|
||||
private val thumbView: View
|
||||
private val popupView: TextView
|
||||
|
||||
// Touch values
|
||||
private val thumbWidth: Int
|
||||
private val thumbHeight: Int
|
||||
private var thumbOffset = 0
|
||||
private var downX = 0f
|
||||
private var downY = 0f
|
||||
private var lastY = 0f
|
||||
private var dragStartY = 0f
|
||||
private var dragStartThumbOffset = 0
|
||||
|
||||
// State
|
||||
private var dragging = false
|
||||
private var showingScrollbar = false
|
||||
private var showingPopup = false
|
||||
|
||||
private val childRect = Rect()
|
||||
|
||||
private val hideScrollbarRunnable = Runnable {
|
||||
if (!dragging) {
|
||||
hideScrollbar()
|
||||
}
|
||||
}
|
||||
|
||||
private val scrollerPadding = Rect(0, 0, 0, 0)
|
||||
|
||||
init {
|
||||
val thumbDrawable = context.getDrawableSafe(R.drawable.ui_scroll_thumb)
|
||||
|
||||
trackView = View(context)
|
||||
thumbView =
|
||||
View(context).apply {
|
||||
alpha = 0f
|
||||
background = thumbDrawable
|
||||
}
|
||||
|
||||
popupView =
|
||||
AppCompatTextView(context).apply {
|
||||
alpha = 0f
|
||||
layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
minimumWidth = context.getDimenSizeSafe(R.dimen.popup_min_width)
|
||||
minimumHeight = context.getDimenSizeSafe(R.dimen.size_btn_large)
|
||||
|
||||
(layoutParams as FrameLayout.LayoutParams).apply {
|
||||
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
||||
marginEnd = context.getDimenOffsetSafe(R.dimen.spacing_small)
|
||||
}
|
||||
|
||||
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
|
||||
setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary))
|
||||
|
||||
background = FastScrollPopupDrawable(context)
|
||||
elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat()
|
||||
ellipsize = TextUtils.TruncateAt.MIDDLE
|
||||
gravity = Gravity.CENTER
|
||||
includeFontPadding = false
|
||||
isSingleLine = true
|
||||
}
|
||||
|
||||
thumbWidth = thumbDrawable.intrinsicWidth
|
||||
thumbHeight = thumbDrawable.intrinsicHeight
|
||||
|
||||
check(thumbWidth >= 0)
|
||||
check(thumbHeight >= 0)
|
||||
|
||||
overlay.add(trackView)
|
||||
overlay.add(thumbView)
|
||||
overlay.add(popupView)
|
||||
|
||||
|
@ -195,42 +195,33 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
private fun onPreDraw() {
|
||||
updateScrollbarState()
|
||||
|
||||
trackView.layoutDirection = layoutDirection
|
||||
thumbView.layoutDirection = layoutDirection
|
||||
popupView.layoutDirection = layoutDirection
|
||||
|
||||
val trackLeft =
|
||||
if (isRtl) {
|
||||
scrollerPadding.left
|
||||
} else {
|
||||
width - scrollerPadding.right - thumbWidth
|
||||
}
|
||||
|
||||
trackView.layout(
|
||||
trackLeft, scrollerPadding.top, trackLeft + thumbWidth, height - scrollerPadding.bottom)
|
||||
|
||||
val thumbLeft =
|
||||
if (isRtl) {
|
||||
scrollerPadding.left
|
||||
thumbPadding.left
|
||||
} else {
|
||||
width - scrollerPadding.right - thumbWidth
|
||||
width - thumbPadding.right - thumbWidth
|
||||
}
|
||||
|
||||
val thumbTop = scrollerPadding.top + thumbOffset
|
||||
val thumbTop = thumbPadding.top + thumbOffset
|
||||
|
||||
thumbView.layout(thumbLeft, thumbTop, thumbLeft + thumbWidth, thumbTop + thumbHeight)
|
||||
|
||||
val firstPos = firstAdapterPos
|
||||
val popupText =
|
||||
if (firstPos != NO_POSITION) {
|
||||
popupProvider?.invoke(firstPos) ?: ""
|
||||
popupProvider?.invoke(firstPos)?.ifEmpty { null }
|
||||
} else {
|
||||
""
|
||||
null
|
||||
}
|
||||
|
||||
popupView.isInvisible = popupText.isEmpty()
|
||||
// Lay out the popup view
|
||||
|
||||
if (popupText.isNotEmpty()) {
|
||||
popupView.isInvisible = popupText == null
|
||||
|
||||
if (popupText != null) {
|
||||
val popupLayoutParams = popupView.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
if (popupView.text != popupText) {
|
||||
|
@ -239,8 +230,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
val widthMeasureSpec =
|
||||
ViewGroup.getChildMeasureSpec(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
scrollerPadding.left +
|
||||
scrollerPadding.right +
|
||||
thumbPadding.left +
|
||||
thumbPadding.right +
|
||||
thumbWidth +
|
||||
popupLayoutParams.leftMargin +
|
||||
popupLayoutParams.rightMargin,
|
||||
|
@ -249,8 +240,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
val heightMeasureSpec =
|
||||
ViewGroup.getChildMeasureSpec(
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
||||
scrollerPadding.top +
|
||||
scrollerPadding.bottom +
|
||||
thumbPadding.top +
|
||||
thumbPadding.bottom +
|
||||
popupLayoutParams.topMargin +
|
||||
popupLayoutParams.bottomMargin,
|
||||
popupLayoutParams.height)
|
||||
|
@ -262,39 +253,23 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
val popupHeight = popupView.measuredHeight
|
||||
val popupLeft =
|
||||
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
||||
scrollerPadding.left + thumbWidth + popupLayoutParams.leftMargin
|
||||
thumbPadding.left + thumbWidth + popupLayoutParams.leftMargin
|
||||
} else {
|
||||
width -
|
||||
scrollerPadding.right -
|
||||
thumbPadding.right -
|
||||
thumbWidth -
|
||||
popupLayoutParams.rightMargin -
|
||||
popupWidth
|
||||
}
|
||||
|
||||
// We handle RTL separately, so it's okay if Gravity.RIGHT is used here
|
||||
@SuppressLint("RtlHardcoded")
|
||||
val popupAnchorY =
|
||||
when (popupLayoutParams.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
|
||||
Gravity.CENTER_HORIZONTAL -> popupHeight / 2
|
||||
Gravity.RIGHT -> popupHeight
|
||||
else -> 0
|
||||
}
|
||||
|
||||
val thumbAnchorY =
|
||||
when (popupLayoutParams.gravity and Gravity.VERTICAL_GRAVITY_MASK) {
|
||||
Gravity.CENTER_VERTICAL -> {
|
||||
thumbView.paddingTop +
|
||||
(thumbHeight - thumbView.paddingTop - thumbView.paddingBottom) / 2
|
||||
}
|
||||
Gravity.BOTTOM -> thumbHeight - thumbView.paddingBottom
|
||||
else -> thumbView.paddingTop
|
||||
}
|
||||
val popupAnchorY = popupHeight / 2
|
||||
val thumbAnchorY = thumbView.paddingTop
|
||||
|
||||
val popupTop =
|
||||
MathUtils.clamp(
|
||||
thumbTop + thumbAnchorY - popupAnchorY,
|
||||
scrollerPadding.top + popupLayoutParams.topMargin,
|
||||
height - scrollerPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
|
||||
thumbPadding.top + popupLayoutParams.topMargin,
|
||||
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
|
||||
|
||||
popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
|
||||
}
|
||||
|
@ -317,7 +292,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
||||
super.onApplyWindowInsets(insets)
|
||||
val bars = insets.systemBarInsetsCompat
|
||||
scrollerPadding.bottom = bars.bottom
|
||||
thumbPadding.bottom = bars.bottom
|
||||
return insets
|
||||
}
|
||||
|
||||
|
@ -345,38 +320,36 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
downX = eventX
|
||||
downY = eventY
|
||||
|
||||
val scrollX = trackView.scrollX
|
||||
val isInScrollbar =
|
||||
eventX >= thumbView.left - scrollX && eventX < thumbView.right - scrollX
|
||||
|
||||
if (trackView.alpha > 0 && isInScrollbar) {
|
||||
if (eventX >= thumbView.left && eventX < thumbView.right) {
|
||||
dragStartY = eventY
|
||||
|
||||
if (isInViewTouchTarget(thumbView, eventX, eventY)) {
|
||||
if (thumbView.isUnder(eventX, eventY, minTouchTargetSize)) {
|
||||
dragStartThumbOffset = thumbOffset
|
||||
} else {
|
||||
dragStartThumbOffset =
|
||||
(eventY - scrollerPadding.top - thumbHeight / 2f).toInt()
|
||||
(eventY - thumbPadding.top - thumbHeight / 2f).toInt()
|
||||
scrollToThumbOffset(dragStartThumbOffset)
|
||||
}
|
||||
|
||||
setDragging(true)
|
||||
dragging = true
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (!dragging &&
|
||||
isInViewTouchTarget(trackView, downX, downY) &&
|
||||
thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) &&
|
||||
abs(eventY - downY) > touchSlop) {
|
||||
if (isInViewTouchTarget(thumbView, downX, downY)) {
|
||||
|
||||
if (thumbView.isUnder(downX, downY, minTouchTargetSize)) {
|
||||
dragStartY = lastY
|
||||
dragStartThumbOffset = thumbOffset
|
||||
} else {
|
||||
dragStartY = eventY
|
||||
dragStartThumbOffset =
|
||||
(eventY - scrollerPadding.top - thumbHeight / 2f).toInt()
|
||||
(eventY - thumbPadding.top - thumbHeight / 2f).toInt()
|
||||
scrollToThumbOffset(dragStartThumbOffset)
|
||||
}
|
||||
setDragging(true)
|
||||
|
||||
dragging = true
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
|
@ -384,49 +357,13 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
scrollToThumbOffset(thumbOffset)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> setDragging(false)
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> dragging = false
|
||||
}
|
||||
|
||||
lastY = eventY
|
||||
return dragging
|
||||
}
|
||||
|
||||
private fun isInViewTouchTarget(view: View, x: Float, y: Float): Boolean {
|
||||
return isInTouchTarget(x, view.left - scrollX, view.right - scrollX, width) &&
|
||||
isInTouchTarget(y, view.top - scrollY, view.bottom - scrollY, height)
|
||||
}
|
||||
|
||||
private fun isInTouchTarget(
|
||||
position: Float,
|
||||
viewStart: Int,
|
||||
viewEnd: Int,
|
||||
parentEnd: Int
|
||||
): Boolean {
|
||||
val viewSize = viewEnd - viewStart
|
||||
|
||||
if (viewSize >= minTouchTargetSize) {
|
||||
return position >= viewStart && position < viewEnd
|
||||
}
|
||||
|
||||
var touchTargetStart = viewStart - (minTouchTargetSize - viewSize) / 2
|
||||
|
||||
if (touchTargetStart < 0) {
|
||||
touchTargetStart = 0
|
||||
}
|
||||
|
||||
var touchTargetEnd = touchTargetStart + minTouchTargetSize
|
||||
if (touchTargetEnd > parentEnd) {
|
||||
touchTargetEnd = parentEnd
|
||||
touchTargetStart = touchTargetEnd - minTouchTargetSize
|
||||
|
||||
if (touchTargetStart < 0) {
|
||||
touchTargetStart = 0
|
||||
}
|
||||
}
|
||||
|
||||
return position >= touchTargetStart && position < touchTargetEnd
|
||||
}
|
||||
|
||||
private fun scrollToThumbOffset(thumbOffset: Int) {
|
||||
val clampedThumbOffset = MathUtils.clamp(thumbOffset, 0, thumbOffsetRange)
|
||||
|
||||
|
@ -464,54 +401,28 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
}
|
||||
}
|
||||
|
||||
private fun setDragging(isDragging: Boolean) {
|
||||
if (dragging == isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
dragging = isDragging
|
||||
|
||||
if (dragging) {
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
}
|
||||
|
||||
trackView.isPressed = dragging
|
||||
thumbView.isPressed = dragging
|
||||
|
||||
if (dragging) {
|
||||
removeCallbacks(hideScrollbarRunnable)
|
||||
showScrollbar()
|
||||
showPopup()
|
||||
} else {
|
||||
postAutoHideScrollbar()
|
||||
hidePopup()
|
||||
}
|
||||
|
||||
onDragListener?.invoke(isDragging)
|
||||
}
|
||||
|
||||
// --- SCROLLBAR APPEARANCE ---
|
||||
|
||||
private fun postAutoHideScrollbar() {
|
||||
removeCallbacks(hideScrollbarRunnable)
|
||||
postDelayed(hideScrollbarRunnable, AUTO_HIDE_SCROLLBAR_DELAY_MILLIS.toLong())
|
||||
removeCallbacks(hideThumbRunnable)
|
||||
postDelayed(hideThumbRunnable, AUTO_HIDE_SCROLLBAR_DELAY_MILLIS.toLong())
|
||||
}
|
||||
|
||||
private fun showScrollbar() {
|
||||
if (showingScrollbar) {
|
||||
if (showingThumb) {
|
||||
return
|
||||
}
|
||||
|
||||
showingScrollbar = true
|
||||
showingThumb = true
|
||||
animateView(thumbView, 1f)
|
||||
}
|
||||
|
||||
private fun hideScrollbar() {
|
||||
if (!showingScrollbar) {
|
||||
if (!showingThumb) {
|
||||
return
|
||||
}
|
||||
|
||||
showingScrollbar = false
|
||||
showingThumb = false
|
||||
animateView(thumbView, 0f)
|
||||
}
|
||||
|
||||
|
@ -539,12 +450,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
|
||||
// --- LAYOUT STATE ---
|
||||
|
||||
private val isRtl: Boolean
|
||||
get() = layoutDirection == LAYOUT_DIRECTION_RTL
|
||||
|
||||
private val thumbOffsetRange: Int
|
||||
get() {
|
||||
return height - scrollerPadding.top - scrollerPadding.bottom - thumbHeight
|
||||
return height - thumbPadding.top - thumbPadding.bottom - thumbHeight
|
||||
}
|
||||
|
||||
private val scrollRange: Int
|
||||
|
|
|
@ -44,7 +44,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
class PlaybackFragment : Fragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private var mLastBinding: FragmentPlaybackBinding? = null
|
||||
private var lastBinding: FragmentPlaybackBinding? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -55,7 +55,7 @@ class PlaybackFragment : Fragment() {
|
|||
val queueItem: MenuItem
|
||||
|
||||
// See onDestroyView for why we do this
|
||||
mLastBinding = binding
|
||||
lastBinding = binding
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
@ -157,8 +157,8 @@ class PlaybackFragment : Fragment() {
|
|||
|
||||
// playbackSong will leak if we don't disable marquee, keep the binding around
|
||||
// so that we can turn it off when we destroy the view.
|
||||
mLastBinding?.playbackSong?.isSelected = false
|
||||
mLastBinding = null
|
||||
lastBinding?.playbackSong?.isSelected = false
|
||||
lastBinding = null
|
||||
}
|
||||
|
||||
private fun navigateUp() {
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.oxycblt.auxio.util.disableDropShadowCompat
|
|||
import org.oxycblt.auxio.util.getAttrColorSafe
|
||||
import org.oxycblt.auxio.util.getDimenSafe
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
import org.oxycblt.auxio.util.isUnder
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.pxOfDp
|
||||
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
||||
|
@ -458,27 +459,25 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
|
|||
return super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
val adx = abs(ev.x - initMotionX)
|
||||
val ady = abs(ev.y - initMotionY)
|
||||
val dragSlop = dragHelper.touchSlop
|
||||
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
initMotionX = ev.x
|
||||
initMotionY = ev.y
|
||||
|
||||
if (!playbackContainerView.isUnder(ev.x.toInt(), ev.y.toInt())) {
|
||||
if (!playbackContainerView.isUnder(ev.x, ev.y)) {
|
||||
// Pointer is not on our view, do not intercept this event
|
||||
dragHelper.cancel()
|
||||
return false
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val pointerUnder = playbackContainerView.isUnder(ev.x.toInt(), ev.y.toInt())
|
||||
val motionUnder =
|
||||
playbackContainerView.isUnder(initMotionX.toInt(), initMotionY.toInt())
|
||||
val adx = abs(ev.x - initMotionX)
|
||||
val ady = abs(ev.y - initMotionY)
|
||||
|
||||
if (!(pointerUnder || motionUnder) || ady > dragSlop && adx > ady) {
|
||||
val pointerUnder = playbackContainerView.isUnder(ev.x, ev.y)
|
||||
val motionUnder = playbackContainerView.isUnder(initMotionX, initMotionY)
|
||||
|
||||
if (!(pointerUnder || motionUnder) || ady > dragHelper.touchSlop && adx > ady) {
|
||||
// Pointer has moved beyond our control, do not intercept this event
|
||||
dragHelper.cancel()
|
||||
return false
|
||||
|
@ -502,22 +501,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
|
|||
}
|
||||
}
|
||||
|
||||
private fun View.isUnder(x: Int, y: Int): Boolean {
|
||||
val viewLocation = IntArray(2)
|
||||
getLocationOnScreen(viewLocation)
|
||||
|
||||
val parentLocation = IntArray(2)
|
||||
(parent as View).getLocationOnScreen(parentLocation)
|
||||
|
||||
val screenX = parentLocation[0] + x
|
||||
val screenY = parentLocation[1] + y
|
||||
|
||||
val inX = screenX >= viewLocation[0] && screenX < viewLocation[0] + width
|
||||
val inY = screenY >= viewLocation[1] && screenY < viewLocation[1] + height
|
||||
|
||||
return inX && inY
|
||||
}
|
||||
|
||||
private val ViewDragHelper.isDragging: Boolean
|
||||
get() {
|
||||
// We can't grab the drag state outside of a callback, but that's stupid and I don't
|
||||
|
|
|
@ -441,6 +441,7 @@ class PlaybackService :
|
|||
// Technically the MediaSession seems to handle bluetooth events on their
|
||||
// own, but keep this around as a fallback in the case that the former fails
|
||||
// for whatever reason.
|
||||
// TODO: Remove this since the headset hook KeyEvent should be fine enough.
|
||||
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
|
||||
when (intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)) {
|
||||
AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> pauseFromPlug()
|
||||
|
@ -459,7 +460,7 @@ class PlaybackService :
|
|||
initialHeadsetPlugEventHandled = true
|
||||
}
|
||||
|
||||
// I have never seen this ever happen but it might be useful
|
||||
// I have never seen this happen but it might be useful
|
||||
AudioManager.ACTION_AUDIO_BECOMING_NOISY -> pauseFromPlug()
|
||||
|
||||
// --- AUXIO EVENTS ---
|
||||
|
@ -484,9 +485,7 @@ class PlaybackService :
|
|||
* that friendly
|
||||
* 2. There is a bug where playback will always start when this service starts, mostly due
|
||||
* to AudioManager.ACTION_HEADSET_PLUG always firing on startup. This is fixed, but I fear
|
||||
* that it may not work on OEM skins that for whatever reason don't make this action fire.
|
||||
* TODO: Figure out how players like Retro are able to get autoplay working with bluetooth
|
||||
* headsets
|
||||
* that it may not work on OEM skins that for whatever reason don't make this action fire.\
|
||||
*/
|
||||
private fun maybeResumeFromPlug() {
|
||||
if (playbackManager.song != null &&
|
||||
|
@ -497,12 +496,7 @@ class PlaybackService :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause from a headset plug.
|
||||
*
|
||||
* TODO: Find a way to centralize this stuff into a single BroadcastReceiver instead of the
|
||||
* weird disjointed arrangement between MediaSession and this.
|
||||
*/
|
||||
/** Pause from a headset plug. */
|
||||
private fun pauseFromPlug() {
|
||||
if (playbackManager.song != null) {
|
||||
logD("Device disconnected, pausing")
|
||||
|
|
|
@ -56,8 +56,11 @@ fun Fragment.newMenu(anchor: View, data: Item, flag: Int = ActionMenu.FLAG_NONE)
|
|||
* @throws IllegalStateException When there is no menu for this specific datatype/flag
|
||||
* @author OxygenCobalt
|
||||
*
|
||||
* TODO: Stop scrolling when a menu is open TODO: Prevent duplicate menus from showing up TODO:
|
||||
* Maybe replace this with a bottom sheet?
|
||||
* TODO: Stop scrolling when a menu is open
|
||||
*
|
||||
* TODO: Prevent duplicate menus from showing up
|
||||
*
|
||||
* TODO: Maybe replace this with a bottom sheet?
|
||||
*/
|
||||
class ActionMenu(
|
||||
activity: AppCompatActivity,
|
||||
|
|
|
@ -20,7 +20,9 @@ package org.oxycblt.auxio.util
|
|||
import android.content.res.ColorStateList
|
||||
import android.graphics.Insets
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import androidx.annotation.ColorRes
|
||||
|
@ -73,6 +75,50 @@ fun View.disableDropShadowCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
fun View.isUnder(x: Float, y: Float, minTouchTargetSize: Int = 0): Boolean {
|
||||
return isUnderImpl(x, left, right, (parent as View).width, minTouchTargetSize) &&
|
||||
isUnderImpl(y, top, bottom, (parent as View).height, minTouchTargetSize)
|
||||
}
|
||||
|
||||
private fun isUnderImpl(
|
||||
position: Float,
|
||||
viewStart: Int,
|
||||
viewEnd: Int,
|
||||
parentEnd: Int,
|
||||
minTouchTargetSize: Int
|
||||
): Boolean {
|
||||
val viewSize = viewEnd - viewStart
|
||||
|
||||
if (viewSize >= minTouchTargetSize) {
|
||||
return position >= viewStart && position < viewEnd
|
||||
}
|
||||
|
||||
Log.d("Auxio.ViewUtil", "isInTouchTarget: $minTouchTargetSize")
|
||||
|
||||
var touchTargetStart = viewStart - (minTouchTargetSize - viewSize) / 2
|
||||
|
||||
if (touchTargetStart < 0) {
|
||||
touchTargetStart = 0
|
||||
}
|
||||
|
||||
var touchTargetEnd = touchTargetStart + minTouchTargetSize
|
||||
if (touchTargetEnd > parentEnd) {
|
||||
touchTargetEnd = parentEnd
|
||||
touchTargetStart = touchTargetEnd - minTouchTargetSize
|
||||
|
||||
if (touchTargetStart < 0) {
|
||||
touchTargetStart = 0
|
||||
}
|
||||
}
|
||||
|
||||
return position >= touchTargetStart && position < touchTargetEnd
|
||||
}
|
||||
|
||||
val View.isRtl: Boolean
|
||||
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
val Drawable.isRtl: Boolean
|
||||
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
|
||||
/**
|
||||
* Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view
|
||||
* that properly follows all the frustrating changes that were made between 8-11.
|
||||
|
|
|
@ -30,8 +30,11 @@
|
|||
<dimen name="elevation_small">2dp</dimen>
|
||||
<dimen name="elevation_normal">4dp</dimen>
|
||||
|
||||
<dimen name="popup_min_width">78dp</dimen>
|
||||
<dimen name="popup_padding_end">28dp</dimen>
|
||||
<dimen name="fast_scroll_popup_min_width">80dp</dimen>
|
||||
<dimen name="fast_scroll_popup_min_height">64dp</dimen>
|
||||
<dimen name="fast_scroll_popup_padding_start">@dimen/spacing_medium</dimen>
|
||||
<dimen name="fast_scroll_popup_padding_end">28dp</dimen>
|
||||
<dimen name="fast_scroll_thumb_touch_target_size">16dp</dimen>
|
||||
|
||||
<dimen name="slider_thumb_radius">6dp</dimen>
|
||||
<dimen name="slider_halo_radius">12dp</dimen>
|
||||
|
|
|
@ -19,7 +19,7 @@ import re
|
|||
|
||||
# WARNING: THE EXOPLAYER VERSION MUST BE KEPT IN LOCK-STEP WITH THE FLAC EXTENSION AND
|
||||
# THE GRADLE DEPENDENCY. IF NOT, VERY UNFRIENDLY BUILD FAILURES AND CRASHES MAY ENSUE.
|
||||
EXO_VERSION = "2.17.0"
|
||||
EXO_VERSION = "2.17.1"
|
||||
FLAC_VERSION = "1.3.2"
|
||||
|
||||
FATAL="\033[1;31m"
|
||||
|
|
Loading…
Reference in a new issue