queue: reimplement with bottom sheet
Re-implement the queue, now leveraging a bottom sheet too. This makes the queue much easier to open, and actually plays along with the new transition system. I really hope this doesn't have a stupid gotcha that ruins the UX. Please. Please. Please.
This commit is contained in:
parent
cc3cb343b0
commit
a4fa8a84fa
13 changed files with 292 additions and 157 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -15,6 +15,3 @@ captures/
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
*.iml
|
*.iml
|
||||||
.cxx
|
.cxx
|
||||||
|
|
||||||
# Patched material
|
|
||||||
app/src/main/com/google/android/material
|
|
||||||
|
|
|
@ -9,6 +9,10 @@ at the cost of longer loading times
|
||||||
- Added support for date tags, including more fine-grained dates [#159, dependent on this feature]
|
- Added support for date tags, including more fine-grained dates [#159, dependent on this feature]
|
||||||
- Added support for release types signifying EPs, Singles, Compilations, and more [#158, dependent on this feature]
|
- Added support for release types signifying EPs, Singles, Compilations, and more [#158, dependent on this feature]
|
||||||
- Added basic awareness of multi-value vorbis tags [#197, dependent on this feature]
|
- Added basic awareness of multi-value vorbis tags [#197, dependent on this feature]
|
||||||
|
- Completely reworked the main playback UI
|
||||||
|
- Queue can now be swiped up [#92]
|
||||||
|
- Playing song is now shown in queue [#92]
|
||||||
|
- Added ability to play songs from queue [#92]
|
||||||
- Added Last Added sorting
|
- Added Last Added sorting
|
||||||
- Search now takes sort tags and file names in account [#184]
|
- Search now takes sort tags and file names in account [#184]
|
||||||
- Added option to clear playback state in settings
|
- Added option to clear playback state in settings
|
||||||
|
|
|
@ -739,7 +739,6 @@ public class NeoBottomSheetBehavior<V extends View> extends CoordinatorLayout.Be
|
||||||
nestedScrolled = true;
|
nestedScrolled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopNestedScroll(
|
public void onStopNestedScroll(
|
||||||
@NonNull CoordinatorLayout coordinatorLayout,
|
@NonNull CoordinatorLayout coordinatorLayout,
|
||||||
|
@ -761,13 +760,12 @@ public void onStopNestedScroll(
|
||||||
if (fitToContents) {
|
if (fitToContents) {
|
||||||
targetState = STATE_EXPANDED;
|
targetState = STATE_EXPANDED;
|
||||||
} else {
|
} else {
|
||||||
// MODIFICATION: Make nested scrolling respond to shouldSkipHalfExpandedStateWhenDragging
|
|
||||||
int currentTop = child.getTop();
|
int currentTop = child.getTop();
|
||||||
if (currentTop < halfExpandedOffset) {
|
if (currentTop < halfExpandedOffset) {
|
||||||
targetState = STATE_EXPANDED;
|
targetState = STATE_EXPANDED;
|
||||||
} else {
|
} else {
|
||||||
if (shouldSkipHalfExpandedStateWhenDragging()) {
|
if (shouldSkipHalfExpandedStateWhenDragging()) {
|
||||||
targetState = STATE_COLLAPSED;
|
targetState = STATE_EXPANDED;
|
||||||
} else {
|
} else {
|
||||||
targetState = STATE_HALF_EXPANDED;
|
targetState = STATE_HALF_EXPANDED;
|
||||||
}
|
}
|
||||||
|
@ -795,15 +793,10 @@ public void onStopNestedScroll(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// MODIFICATION: Make nested scrolling respond to shouldSkipHalfExpandedStateWhenDragging
|
if (shouldSkipHalfExpandedStateWhenDragging() || Math.abs(currentTop - halfExpandedOffset) >= Math.abs(currentTop - collapsedOffset)) {
|
||||||
if (shouldSkipHalfExpandedStateWhenDragging()) {
|
|
||||||
targetState = STATE_COLLAPSED;
|
targetState = STATE_COLLAPSED;
|
||||||
} else {
|
} else {
|
||||||
if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) {
|
|
||||||
targetState = STATE_HALF_EXPANDED;
|
targetState = STATE_HALF_EXPANDED;
|
||||||
} else {
|
|
||||||
targetState = STATE_COLLAPSED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -812,16 +805,11 @@ public void onStopNestedScroll(
|
||||||
targetState = STATE_COLLAPSED;
|
targetState = STATE_COLLAPSED;
|
||||||
} else {
|
} else {
|
||||||
// Settle to nearest height.
|
// Settle to nearest height.
|
||||||
// MODIFICATION: Make nested scrolling respond to shouldSkipHalfExpandedStateWhenDragging
|
|
||||||
int currentTop = child.getTop();
|
int currentTop = child.getTop();
|
||||||
if (shouldSkipHalfExpandedStateWhenDragging()) {
|
if (shouldSkipHalfExpandedStateWhenDragging() || Math.abs(currentTop - halfExpandedOffset) >= Math.abs(currentTop - collapsedOffset)) {
|
||||||
targetState = STATE_COLLAPSED;
|
targetState = STATE_COLLAPSED;
|
||||||
} else {
|
} else {
|
||||||
if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) {
|
|
||||||
targetState = STATE_HALF_EXPANDED;
|
targetState = STATE_HALF_EXPANDED;
|
||||||
} else {
|
|
||||||
targetState = STATE_COLLAPSED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,10 @@ import androidx.core.view.isInvisible
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
|
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
|
||||||
import com.google.android.material.transition.MaterialFadeThrough
|
import com.google.android.material.transition.MaterialFadeThrough
|
||||||
|
import java.util.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
||||||
|
@ -35,13 +37,11 @@ import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackSheetBehavior
|
import org.oxycblt.auxio.playback.PlaybackSheetBehavior
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
import org.oxycblt.auxio.playback.queue.QueueSheetBehavior
|
||||||
import org.oxycblt.auxio.ui.MainNavigationAction
|
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.*
|
||||||
import org.oxycblt.auxio.util.collect
|
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
|
||||||
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
||||||
|
@ -85,7 +85,27 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
override fun onStateChanged(bottomSheet: View, newState: Int) {}
|
override fun onStateChanged(bottomSheet: View, newState: Int) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
binding.root.post { handleSheetTransitions() }
|
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
|
||||||
|
|
||||||
|
queueSheetBehavior.addBottomSheetCallback(
|
||||||
|
object : NeoBottomSheetBehavior.BottomSheetCallback() {
|
||||||
|
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||||
|
handleSheetTransitions()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
|
playbackSheetBehavior.isDraggable =
|
||||||
|
!playbackSheetBehavior.isHideable &&
|
||||||
|
newState == BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.root.post {
|
||||||
|
handleSheetTransitions()
|
||||||
|
playbackSheetBehavior.isDraggable =
|
||||||
|
!playbackSheetBehavior.isHideable &&
|
||||||
|
queueSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
|
@ -109,8 +129,10 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
val playbackSheetBehavior =
|
val playbackSheetBehavior =
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||||
|
|
||||||
|
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
|
||||||
|
|
||||||
val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f)
|
val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f)
|
||||||
val queueRatio = 0f
|
val queueRatio = max(queueSheetBehavior.calculateSlideOffset(), 0f)
|
||||||
|
|
||||||
val outRatio = 1 - playbackRatio
|
val outRatio = 1 - playbackRatio
|
||||||
val halfOutRatio = min(playbackRatio * 2, 1f)
|
val halfOutRatio = min(playbackRatio * 2, 1f)
|
||||||
|
@ -118,20 +140,26 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
val halfOutQueueRatio = min(queueRatio * 2, 1f)
|
val halfOutQueueRatio = min(queueRatio * 2, 1f)
|
||||||
val halfInQueueRatio = max(queueRatio - 0.5f, 0f) * 2
|
val halfInQueueRatio = max(queueRatio - 0.5f, 0f) * 2
|
||||||
|
|
||||||
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outRatio * 255).toInt()
|
|
||||||
binding.playbackSheet.translationZ = 3f * outRatio
|
|
||||||
binding.playbackPanelFragment.alpha = min(halfInPlaybackRatio, 1 - halfOutQueueRatio)
|
|
||||||
// binding.queueRecycler.alpha = max(queueOffset, 0f)
|
|
||||||
|
|
||||||
binding.exploreNavHost.apply {
|
binding.exploreNavHost.apply {
|
||||||
alpha = outRatio
|
alpha = outRatio
|
||||||
isInvisible = alpha == 0f
|
isInvisible = alpha == 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.playbackSheet.translationZ = 3f * outRatio
|
||||||
|
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outRatio * 255).toInt()
|
||||||
|
|
||||||
binding.playbackBarFragment.apply {
|
binding.playbackBarFragment.apply {
|
||||||
alpha = max(1 - halfOutRatio, halfInQueueRatio)
|
alpha = max(1 - halfOutRatio, halfInQueueRatio)
|
||||||
lastInsets?.let { translationY = it.systemWindowInsetTop * halfOutRatio }
|
isInvisible = alpha == 0f
|
||||||
|
lastInsets?.let { translationY = it.systemBarInsetsCompat.top * halfOutRatio }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.playbackPanelFragment.apply {
|
||||||
|
alpha = min(halfInPlaybackRatio, 1 - halfOutQueueRatio)
|
||||||
|
isInvisible = alpha == 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.queueFragment.alpha = queueRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMainNavigation(action: MainNavigationAction?) {
|
private fun handleMainNavigation(action: MainNavigationAction?) {
|
||||||
|
@ -139,16 +167,8 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
|
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
when (action) {
|
when (action) {
|
||||||
is MainNavigationAction.Expand -> {
|
is MainNavigationAction.Expand -> tryExpandAll()
|
||||||
val playbackSheetBehavior =
|
is MainNavigationAction.Collapse -> tryCollapseAll()
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
|
||||||
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED
|
|
||||||
}
|
|
||||||
is MainNavigationAction.Collapse -> {
|
|
||||||
val playbackSheetBehavior =
|
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
|
||||||
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
|
|
||||||
}
|
|
||||||
is MainNavigationAction.Settings ->
|
is MainNavigationAction.Settings ->
|
||||||
findNavController().navigate(MainFragmentDirections.actionShowSettings())
|
findNavController().navigate(MainFragmentDirections.actionShowSettings())
|
||||||
is MainNavigationAction.About ->
|
is MainNavigationAction.About ->
|
||||||
|
@ -163,13 +183,7 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
|
|
||||||
private fun handleExploreNavigation(item: Music?) {
|
private fun handleExploreNavigation(item: Music?) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
val binding = requireBinding()
|
tryCollapseAll()
|
||||||
val playbackSheetBehavior =
|
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
|
||||||
|
|
||||||
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) {
|
|
||||||
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,11 +207,7 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
inner class DynamicBackPressedCallback : OnBackPressedCallback(false) {
|
inner class DynamicBackPressedCallback : OnBackPressedCallback(false) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
val playbackSheetBehavior =
|
if (!tryCollapseAll()) {
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
|
||||||
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) {
|
|
||||||
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
|
|
||||||
} else {
|
|
||||||
val navController = binding.exploreNavHost.findNavController()
|
val navController = binding.exploreNavHost.findNavController()
|
||||||
|
|
||||||
if (navController.currentDestination?.id ==
|
if (navController.currentDestination?.id ==
|
||||||
|
@ -211,4 +221,38 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryExpandAll(): Boolean {
|
||||||
|
val binding = requireBinding()
|
||||||
|
val playbackSheetBehavior =
|
||||||
|
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||||
|
|
||||||
|
if (playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN &&
|
||||||
|
playbackSheetBehavior.state != BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
|
playbackSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryCollapseAll(): Boolean {
|
||||||
|
val binding = requireBinding()
|
||||||
|
val playbackSheetBehavior =
|
||||||
|
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||||
|
|
||||||
|
if (playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN &&
|
||||||
|
playbackSheetBehavior.state != BottomSheetBehavior.STATE_COLLAPSED) {
|
||||||
|
playbackSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
|
||||||
|
val queueSheetBehavior =
|
||||||
|
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
|
||||||
|
|
||||||
|
queueSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import kotlin.math.max
|
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
|
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
|
||||||
import org.oxycblt.auxio.music.MusicParent
|
import org.oxycblt.auxio.music.MusicParent
|
||||||
|
@ -38,7 +37,6 @@ import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableSafe
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
import org.oxycblt.auxio.util.systemGestureInsetsCompat
|
|
||||||
import org.oxycblt.auxio.util.textSafe
|
import org.oxycblt.auxio.util.textSafe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,13 +63,8 @@ class PlaybackPanelFragment :
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
binding.root.setOnApplyWindowInsetsListener { view, insets ->
|
binding.root.setOnApplyWindowInsetsListener { view, insets ->
|
||||||
// The playback controls should be inset upwards at least a little bit more than usual,
|
|
||||||
// just for quality of life. While the old 3-button navigation does this for us, when
|
|
||||||
// bar navigation is used, we use the gesture padding to add that extra portion of
|
|
||||||
// space.
|
|
||||||
val bars = insets.systemBarInsetsCompat
|
val bars = insets.systemBarInsetsCompat
|
||||||
val gestures = insets.systemGestureInsetsCompat
|
view.updatePadding(top = bars.top, bottom = bars.bottom)
|
||||||
view.updatePadding(top = bars.top, bottom = max(gestures.bottom, bars.bottom))
|
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,11 @@ 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 kotlin.math.max
|
||||||
import org.oxycblt.auxio.ui.AuxioSheetBehavior
|
import org.oxycblt.auxio.ui.AuxioSheetBehavior
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
import org.oxycblt.auxio.util.systemGestureInsetsCompat
|
||||||
|
|
||||||
class PlaybackSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
class PlaybackSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
||||||
AuxioSheetBehavior<V>(context, attributeSet) {
|
AuxioSheetBehavior<V>(context, attributeSet) {
|
||||||
|
@ -45,7 +48,9 @@ class PlaybackSheetBehavior<V : View>(context: Context, attributeSet: AttributeS
|
||||||
(child as ViewGroup).apply {
|
(child as ViewGroup).apply {
|
||||||
setOnApplyWindowInsetsListener { v, insets ->
|
setOnApplyWindowInsetsListener { v, insets ->
|
||||||
lastInsets = insets
|
lastInsets = insets
|
||||||
peekHeight = getChildAt(0).measuredHeight + insets.systemGestureInsets.bottom
|
val bars = insets.systemBarInsetsCompat
|
||||||
|
val gestures = insets.systemGestureInsetsCompat
|
||||||
|
peekHeight = getChildAt(0).measuredHeight + max(gestures.bottom, bars.bottom)
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.oxycblt.auxio.playback.queue
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
|
@ -54,10 +55,20 @@ private constructor(
|
||||||
val backgroundDrawable =
|
val backgroundDrawable =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
||||||
fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList
|
fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList
|
||||||
|
elevation = binding.context.getDimenSafe(R.dimen.elevation_normal) * 5
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.body.background = backgroundDrawable
|
binding.body.background =
|
||||||
|
LayerDrawable(
|
||||||
|
arrayOf(
|
||||||
|
MaterialShapeDrawable.createWithElevationOverlay(binding.context).apply {
|
||||||
|
fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList
|
||||||
|
elevation = binding.context.getDimenSafe(R.dimen.elevation_normal)
|
||||||
|
},
|
||||||
|
backgroundDrawable))
|
||||||
|
|
||||||
|
backgroundDrawable.alpha = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
|
|
@ -86,12 +86,15 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
logD("Lifting queue item")
|
logD("Lifting queue item")
|
||||||
|
|
||||||
val bg = holder.backgroundDrawable
|
val bg = holder.backgroundDrawable
|
||||||
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_small)
|
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_normal)
|
||||||
holder.itemView
|
holder.itemView
|
||||||
.animate()
|
.animate()
|
||||||
.translationZ(elevation)
|
.translationZ(elevation)
|
||||||
.setDuration(100)
|
.setDuration(100)
|
||||||
.setUpdateListener { bg.elevation = holder.itemView.translationZ }
|
.setUpdateListener {
|
||||||
|
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
||||||
|
logD("in ${bg.alpha} ${holder.itemView.translationZ}")
|
||||||
|
}
|
||||||
.setInterpolator(AccelerateDecelerateInterpolator())
|
.setInterpolator(AccelerateDecelerateInterpolator())
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
|
@ -124,11 +127,15 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
logD("Dropping queue item")
|
logD("Dropping queue item")
|
||||||
|
|
||||||
val bg = holder.backgroundDrawable
|
val bg = holder.backgroundDrawable
|
||||||
|
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_normal)
|
||||||
holder.itemView
|
holder.itemView
|
||||||
.animate()
|
.animate()
|
||||||
.translationZ(0.0f)
|
.translationZ(0f)
|
||||||
.setDuration(100)
|
.setDuration(100)
|
||||||
.setUpdateListener { bg.elevation = holder.itemView.translationZ }
|
.setUpdateListener {
|
||||||
|
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
||||||
|
logD("out ${bg.alpha} ${holder.itemView.translationZ} ${elevation}")
|
||||||
|
}
|
||||||
.setInterpolator(AccelerateDecelerateInterpolator())
|
.setInterpolator(AccelerateDecelerateInterpolator())
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
||||||
|
@ -42,8 +41,6 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
|
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentQueueBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentQueueBinding, savedInstanceState: Bundle?) {
|
||||||
binding.queueToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
|
||||||
|
|
||||||
binding.queueRecycler.apply {
|
binding.queueRecycler.apply {
|
||||||
adapter = queueAdapter
|
adapter = queueAdapter
|
||||||
touchHelper.attachToRecyclerView(this)
|
touchHelper.attachToRecyclerView(this)
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.playback.queue
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import kotlin.math.max
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.ui.AuxioSheetBehavior
|
||||||
|
import org.oxycblt.auxio.util.*
|
||||||
|
|
||||||
|
class QueueSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
||||||
|
AuxioSheetBehavior<V>(context, attributeSet) {
|
||||||
|
private var barHeight = 0
|
||||||
|
private var barSpacing = context.getDimenSizeSafe(R.dimen.spacing_small)
|
||||||
|
|
||||||
|
init {
|
||||||
|
sheetBackgroundDrawable.setCornerSize(context.getDimenSafe(R.dimen.size_corners_medium))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View) =
|
||||||
|
dependency.id == R.id.playback_bar_fragment
|
||||||
|
|
||||||
|
override fun onDependentViewChanged(
|
||||||
|
parent: CoordinatorLayout,
|
||||||
|
child: V,
|
||||||
|
dependency: View
|
||||||
|
): Boolean {
|
||||||
|
val ok = super.onDependentViewChanged(parent, child, dependency)
|
||||||
|
barHeight = dependency.height
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
|
||||||
|
val success = super.onLayoutChild(parent, child, layoutDirection)
|
||||||
|
|
||||||
|
child.setOnApplyWindowInsetsListener { _, insets ->
|
||||||
|
val bars = insets.systemBarInsetsCompat
|
||||||
|
val gestures = insets.systemGestureInsetsCompat
|
||||||
|
|
||||||
|
expandedOffset = bars.top + barHeight + barSpacing
|
||||||
|
peekHeight =
|
||||||
|
(child as ViewGroup).getChildAt(0).height + max(gestures.bottom, bars.bottom)
|
||||||
|
insets.replaceSystemBarInsetsCompat(
|
||||||
|
bars.left, bars.top, bars.right, expandedOffset + bars.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ abstract class AuxioSheetBehavior<V : View>(context: Context, attributeSet: Attr
|
||||||
val sheetBackgroundDrawable =
|
val sheetBackgroundDrawable =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||||
fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList
|
fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList
|
||||||
elevation = context.pxOfDp(elevationNormal).toFloat()
|
elevation = elevationNormal
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
app:navGraph="@navigation/nav_explore"
|
app:navGraph="@navigation/nav_explore"
|
||||||
tools:layout="@layout/fragment_home" />
|
tools:layout="@layout/fragment_home" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/playback_sheet"
|
android:id="@+id/playback_sheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -32,8 +32,51 @@
|
||||||
android:id="@+id/playback_panel_fragment"
|
android:id="@+id/playback_panel_fragment"
|
||||||
android:name="org.oxycblt.auxio.playback.PlaybackPanelFragment"
|
android:name="org.oxycblt.auxio.playback.PlaybackPanelFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="org.oxycblt.auxio.ui.BottomSheetContentViewBehavior" />
|
||||||
|
|
||||||
</FrameLayout>
|
<LinearLayout
|
||||||
|
android:id="@+id/queue_sheet"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_behavior="org.oxycblt.auxio.playback.queue.QueueSheetBehavior"
|
||||||
|
app:behavior_hideable="false">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/handle_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/handle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:paddingBottom="@dimen/spacing_small"
|
||||||
|
android:src="@drawable/ic_down_24"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/lbl_queue"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/handle"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/handle"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/queue_fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:name="org.oxycblt.auxio.playback.queue.QueueFragment" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.oxycblt.auxio.ui.coordinator.EdgeCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
||||||
|
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"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<org.oxycblt.auxio.ui.coordinator.EdgeAppBarLayout
|
|
||||||
style="@style/Widget.Auxio.AppBarLayout"
|
|
||||||
app:liftOnScroll="true"
|
|
||||||
app:liftOnScrollTargetViewId="@id/queue_recycler">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/queue_toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:elevation="0dp"
|
|
||||||
app:navigationIcon="@drawable/ic_down_24"
|
|
||||||
app:title="@string/lbl_queue" />
|
|
||||||
|
|
||||||
</org.oxycblt.auxio.ui.coordinator.EdgeAppBarLayout>
|
|
||||||
|
|
||||||
<org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
|
||||||
android:id="@+id/queue_recycler"
|
android:id="@+id/queue_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -30,5 +10,3 @@
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
tools:listitem="@layout/item_queue_song" />
|
tools:listitem="@layout/item_queue_song" />
|
||||||
|
|
||||||
</org.oxycblt.auxio.ui.coordinator.EdgeCoordinatorLayout>
|
|
Loading…
Reference in a new issue