diff --git a/app/build.gradle b/app/build.gradle index e5d3281f7..12034dc60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,7 +64,7 @@ dependencies { // General implementation "androidx.core:core-ktx:1.7.0" - implementation "androidx.activity:activity-ktx:1.3.1" + implementation "androidx.activity:activity-ktx:1.4.0" implementation 'androidx.fragment:fragment-ktx:1.3.6' // UI @@ -85,7 +85,7 @@ dependencies { implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" // Media - // TODO: Migrate to media2 when I can figure out how to use it + // TODO: Migrate to Media3 implementation "androidx.media:media:1.4.3" // Preferences diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 89aea2c91..35d8b7fd7 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -73,7 +73,7 @@ class MainFragment : Fragment(), PlaybackBarLayout.ActionCallback { binding.mainBarLayout.setPosition(playbackModel.position.value!!) playbackModel.song.observe(viewLifecycleOwner) { song -> - binding.mainBarLayout.setSong(song) + binding.mainBarLayout.setSong(song, animate = true) } playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying -> diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt index f82960aa3..32e086afd 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt @@ -29,6 +29,7 @@ import androidx.annotation.AttrRes import androidx.annotation.StyleRes import androidx.core.view.children import androidx.core.view.updatePadding +import androidx.customview.widget.ViewDragHelper import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.util.systemBarsCompat @@ -49,6 +50,7 @@ class PlaybackBarLayout @JvmOverloads constructor( @StyleRes defStyleRes: Int = 0 ) : ViewGroup(context, attrs, defStyleAttr, defStyleRes) { private val playbackView = CompactPlaybackView(context) + private var barDragHelper = ViewDragHelper.create(this, ViewDragCallback()) private var lastInsets: WindowInsets? = null init { @@ -76,7 +78,11 @@ class PlaybackBarLayout @JvmOverloads constructor( playbackView.measure(barWidthSpec, barHeightSpec) updateWindowInsets() + measureContent() + } + private fun measureContent() { + val barParams = playbackView.layoutParams as LayoutParams val barHeightAdjusted = (playbackView.measuredHeight * barParams.offset).toInt() val contentWidth = measuredWidth @@ -110,7 +116,17 @@ class PlaybackBarLayout @JvmOverloads constructor( 0, height - barHeightAdjusted, width, height + (barHeight - barHeightAdjusted) ) - } else { + } + } + + layoutContent() + } + + private fun layoutContent() { + for (child in children) { + val childParams = child.layoutParams as LayoutParams + + if (!childParams.isBar) { child.layout(0, 0, child.measuredWidth, child.measuredHeight) } } @@ -125,6 +141,12 @@ class PlaybackBarLayout @JvmOverloads constructor( return insets } + override fun computeScroll() { + if (barDragHelper.continueSettling(true)) { + postInvalidateOnAnimation() + } + } + override fun onDetachedFromWindow() { super.onDetachedFromWindow() @@ -174,12 +196,12 @@ class PlaybackBarLayout @JvmOverloads constructor( } } - fun setSong(song: Song?) { + fun setSong(song: Song?, animate: Boolean = false) { if (song != null) { - showBar() + showBar(animate) playbackView.setSong(song) } else { - hideBar() + hideBar(animate) } } @@ -195,33 +217,53 @@ class PlaybackBarLayout @JvmOverloads constructor( playbackView.setCallback(callback) } - private fun showBar() { + private fun showBar(animate: Boolean) { val barParams = playbackView.layoutParams as LayoutParams - if (barParams.offset == 1f) { + if (barParams.shown || barParams.offset == 1f) { return } - barParams.offset = 1f + barParams.shown = true - if (isLaidOut) { - updateWindowInsets() + if (animate) { + barDragHelper.smoothSlideViewTo( + playbackView, playbackView.left, height - playbackView.height + ) + } else { + barParams.offset = 1f + + if (isLaidOut) { + updateWindowInsets() + measureContent() + layoutContent() + } } invalidate() } - private fun hideBar() { + private fun hideBar(animate: Boolean) { val barParams = playbackView.layoutParams as LayoutParams - if (barParams.offset == 0f) { + if (barParams.shown || barParams.offset == 0f) { return } - barParams.offset = 0f + barParams.shown = false - if (isLaidOut) { - updateWindowInsets() + if (animate) { + barDragHelper.smoothSlideViewTo( + playbackView, playbackView.left, height + ) + } else { + barParams.offset = 0f + + if (isLaidOut) { + updateWindowInsets() + measureContent() + layoutContent() + } } invalidate() @@ -251,6 +293,7 @@ class PlaybackBarLayout @JvmOverloads constructor( @Suppress("UNUSED") class LayoutParams : ViewGroup.LayoutParams { var isBar = false + var shown = false var offset = 0f constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) @@ -267,4 +310,42 @@ class PlaybackBarLayout @JvmOverloads constructor( fun onNavToItem() fun onNavToPlayback() } + + private inner class ViewDragCallback : ViewDragHelper.Callback() { + override fun tryCaptureView(child: View, pointerId: Int): Boolean = false + + override fun onViewPositionChanged( + changedView: View, + left: Int, + top: Int, + dx: Int, + dy: Int + ) { + val childRange = getViewVerticalDragRange(changedView) + val childLayoutParams = changedView.layoutParams as LayoutParams + + val height = height + childLayoutParams.offset = (height - top).toFloat() / childRange + + updateWindowInsets() + measureContent() + layoutContent() + } + + override fun getViewVerticalDragRange(child: View): Int { + val childParams = child.layoutParams as LayoutParams + + return if (childParams.isBar) { + child.height + } else { + 0 + } + } + + override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int = child.left + + override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int { + return top.coerceIn(height - getViewVerticalDragRange(child)..height) + } + } } diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index a9abb902b..56c94fc15 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -179,8 +179,7 @@ class WidgetProvider : AppWidgetProvider() { updateAppWidget(name, RemoteViews(views)) } else { // Otherwise, we try our best to backport the responsive behavior to older versions. - // This is mostly a guess based on RemoteView's documentation. It seems to work well - // enough on most launchers. + // This seems to work well enough on most launchers. // Each widget has independent dimensions, so we iterate through them all // and do this for each.