playback: make compact playback ui a view
Change CompactPlaybackFragment into a View. This completely fixes the
issue I tried to band-aid in ae39054
. The code is a bit uglier, but
that's tolerable.
This commit is contained in:
parent
ae39054b63
commit
c1e1329c21
13 changed files with 226 additions and 178 deletions
|
@ -28,10 +28,13 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
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 com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
||||||
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.MusicViewModel
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
|
import org.oxycblt.auxio.playback.PlaybackBarLayout
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
|
@ -39,8 +42,9 @@ import org.oxycblt.auxio.util.logD
|
||||||
* A wrapper around the home fragment that shows the playback fragment and controls
|
* A wrapper around the home fragment that shows the playback fragment and controls
|
||||||
* the more high-level navigation features.
|
* the more high-level navigation features.
|
||||||
*/
|
*/
|
||||||
class MainFragment : Fragment() {
|
class MainFragment : Fragment(), PlaybackBarLayout.ActionCallback {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
private val musicModel: MusicViewModel by activityViewModels()
|
private val musicModel: MusicViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
@ -63,22 +67,26 @@ class MainFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
if (playbackModel.song.value != null) {
|
binding.mainBarLayout.setActionCallback(this)
|
||||||
binding.mainBarLayout.showBar()
|
|
||||||
} else {
|
binding.mainBarLayout.setSong(playbackModel.song.value)
|
||||||
binding.mainBarLayout.hideBar()
|
binding.mainBarLayout.setPlaying(playbackModel.isPlaying.value!!)
|
||||||
}
|
binding.mainBarLayout.setPosition(playbackModel.position.value!!)
|
||||||
|
|
||||||
playbackModel.song.observe(viewLifecycleOwner) { song ->
|
playbackModel.song.observe(viewLifecycleOwner) { song ->
|
||||||
if (song != null) {
|
binding.mainBarLayout.setSong(song)
|
||||||
binding.mainBarLayout.showBar()
|
|
||||||
} else {
|
|
||||||
binding.mainBarLayout.hideBar()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize music loading. Unlike MainFragment, we can not only do this here on startup
|
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
||||||
// but also show a SnackBar in a reasonable place in this fragment.
|
binding.mainBarLayout.setPlaying(isPlaying)
|
||||||
|
}
|
||||||
|
|
||||||
|
playbackModel.position.observe(viewLifecycleOwner) { pos ->
|
||||||
|
binding.mainBarLayout.setPosition(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize music loading. Do it here so that it shows on every fragment that this
|
||||||
|
// one contains.
|
||||||
musicModel.loadMusic(requireContext())
|
musicModel.loadMusic(requireContext())
|
||||||
|
|
||||||
// Handle the music loader response.
|
// Handle the music loader response.
|
||||||
|
@ -132,4 +140,18 @@ class MainFragment : Fragment() {
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPlayPauseClick() {
|
||||||
|
playbackModel.invertPlayingStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNavToPlayback() {
|
||||||
|
findNavController().navigate(
|
||||||
|
MainFragmentDirections.actionGoToPlayback()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNavToItem() {
|
||||||
|
detailModel.navToItem(playbackModel.song.value ?: return)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.canScroll
|
import org.oxycblt.auxio.util.canScroll
|
||||||
import org.oxycblt.auxio.util.resolveAttr
|
import org.oxycblt.auxio.util.resolveAttr
|
||||||
import org.oxycblt.auxio.util.resolveDrawable
|
import org.oxycblt.auxio.util.resolveDrawable
|
||||||
|
import org.oxycblt.auxio.util.systemBarsCompat
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -324,12 +325,14 @@ class FastScrollRecyclerView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
||||||
|
val bars = insets.systemBarsCompat
|
||||||
|
|
||||||
setPadding(
|
setPadding(
|
||||||
initialPadding.left, initialPadding.top, initialPadding.right,
|
initialPadding.left, initialPadding.top, initialPadding.right,
|
||||||
initialPadding.bottom + insets.systemWindowInsetBottom
|
initialPadding.bottom + bars.bottom
|
||||||
)
|
)
|
||||||
|
|
||||||
scrollerPadding.bottom = insets.systemWindowInsetBottom
|
scrollerPadding.bottom = bars.bottom
|
||||||
|
|
||||||
return insets
|
return insets
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 Auxio Project
|
|
||||||
* CompactPlaybackFragment.kt is part of Auxio.
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import org.oxycblt.auxio.MainFragmentDirections
|
|
||||||
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
|
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
|
||||||
import org.oxycblt.auxio.util.logD
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Fragment] that displays the currently played song at a glance, with some basic controls.
|
|
||||||
* Extends into [PlaybackFragment] when clicked on.
|
|
||||||
*
|
|
||||||
* Instantiation is done by FragmentContainerView, **do not instantiate this fragment manually.**
|
|
||||||
* @author OxygenCobalt
|
|
||||||
* TODO: Add more controls to this view depending on screen width
|
|
||||||
*/
|
|
||||||
class CompactPlaybackFragment : Fragment() {
|
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
val binding = FragmentCompactPlaybackBinding.inflate(inflater)
|
|
||||||
|
|
||||||
// --- UI SETUP ---
|
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
|
||||||
binding.playbackModel = playbackModel
|
|
||||||
binding.executePendingBindings()
|
|
||||||
|
|
||||||
binding.root.apply {
|
|
||||||
setOnClickListener {
|
|
||||||
findNavController().navigate(
|
|
||||||
MainFragmentDirections.actionGoToPlayback()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnLongClickListener {
|
|
||||||
detailModel.navToItem(playbackModel.song.value!!)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
|
||||||
|
|
||||||
playbackModel.song.observe(viewLifecycleOwner) { song ->
|
|
||||||
if (song != null) {
|
|
||||||
logD("Updating song display to ${song.name}")
|
|
||||||
|
|
||||||
binding.song = song
|
|
||||||
binding.playbackProgress.max = song.seconds.toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
|
||||||
binding.playbackPlayPause.isActivated = isPlaying
|
|
||||||
}
|
|
||||||
|
|
||||||
logD("Fragment Created")
|
|
||||||
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Auxio Project
|
||||||
|
* CompactPlaybackView.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.drawable.RippleDrawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.ViewCompactPlaybackBinding
|
||||||
|
import org.oxycblt.auxio.music.Song
|
||||||
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
import org.oxycblt.auxio.util.resolveAttr
|
||||||
|
import org.oxycblt.auxio.util.resolveDrawable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A view displaying the playback state in a compact manner. This is only meant to be used
|
||||||
|
* by [PlaybackBarLayout].
|
||||||
|
*/
|
||||||
|
class CompactPlaybackView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = -1
|
||||||
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
private val binding = ViewCompactPlaybackBinding.inflate(context.inflater, this, true)
|
||||||
|
private var mCallback: PlaybackBarLayout.ActionCallback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
id = R.id.playback_bar
|
||||||
|
|
||||||
|
elevation = resources.getDimensionPixelSize(R.dimen.elevation_normal).toFloat()
|
||||||
|
|
||||||
|
// To get a MaterialShapeDrawable to co-exist with a ripple drawable, we need to layer
|
||||||
|
// this drawable on top of the existing ripple drawable. RippleDrawable actually inherits
|
||||||
|
// LayerDrawable though, so we can do this. However, adding a new drawable layer directly
|
||||||
|
// is only available on API 23+, but we're on API 21. So we create a drawable resource with
|
||||||
|
// an empty drawable with a hard-coded ID, filling the drawable in with a
|
||||||
|
// MaterialShapeDrawable at runtime and allowing this code to work on API 21.
|
||||||
|
background = R.drawable.ui_shape_ripple.resolveDrawable(context).apply {
|
||||||
|
val backgroundDrawable = MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||||
|
elevation = this@CompactPlaybackView.elevation
|
||||||
|
fillColor = ColorStateList.valueOf(R.attr.colorSurface.resolveAttr(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
(this as RippleDrawable).setDrawableByLayerId(
|
||||||
|
android.R.id.background, backgroundDrawable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
isClickable = true
|
||||||
|
isFocusable = true
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
mCallback?.onNavToPlayback()
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnLongClickListener {
|
||||||
|
mCallback?.onNavToItem()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.playbackPlayPause.setOnClickListener {
|
||||||
|
mCallback?.onPlayPauseClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSong(song: Song) {
|
||||||
|
binding.song = song
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPlaying(isPlaying: Boolean) {
|
||||||
|
binding.playbackPlayPause.isActivated = isPlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPosition(position: Long) {
|
||||||
|
if (binding.song == null) {
|
||||||
|
binding.playbackProgress.progress = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.playbackProgress.progress = position.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCallback(callback: PlaybackBarLayout.ActionCallback) {
|
||||||
|
mCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCallback() {
|
||||||
|
mCallback = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,25 +19,18 @@
|
||||||
package org.oxycblt.auxio.playback
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Insets
|
import android.graphics.Insets
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.R
|
|
||||||
import org.oxycblt.auxio.util.logD
|
|
||||||
import org.oxycblt.auxio.util.resolveAttr
|
|
||||||
import org.oxycblt.auxio.util.systemBarsCompat
|
import org.oxycblt.auxio.util.systemBarsCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,9 +39,6 @@ import org.oxycblt.auxio.util.systemBarsCompat
|
||||||
* this class was primarily written by me and I plan to expand this layout to become part of
|
* this class was primarily written by me and I plan to expand this layout to become part of
|
||||||
* the playback navigation process.
|
* the playback navigation process.
|
||||||
*
|
*
|
||||||
* TODO: Migrate CompactPlaybackFragment to a view. This is okay, as updates can be delivered
|
|
||||||
* via MainFragment and it would fix the issue where the actual layout won't measure until
|
|
||||||
* the fragment is shown.
|
|
||||||
* TODO: Implement animation
|
* TODO: Implement animation
|
||||||
* TODO: Implement the swipe-up behavior. This needs to occur, as the way the main fragment
|
* TODO: Implement the swipe-up behavior. This needs to occur, as the way the main fragment
|
||||||
* saves state results in'
|
* saves state results in'
|
||||||
|
@ -59,37 +49,18 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
@AttrRes defStyleAttr: Int = 0,
|
@AttrRes defStyleAttr: Int = 0,
|
||||||
@StyleRes defStyleRes: Int = 0
|
@StyleRes defStyleRes: Int = 0
|
||||||
) : ViewGroup(context, attrs, defStyleAttr, defStyleRes) {
|
) : ViewGroup(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
private val barLayout = FrameLayout(context)
|
private val playbackView = CompactPlaybackView(context)
|
||||||
private val playbackFragment = CompactPlaybackFragment()
|
|
||||||
private var lastInsets: WindowInsets? = null
|
private var lastInsets: WindowInsets? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addView(barLayout)
|
addView(playbackView)
|
||||||
|
|
||||||
barLayout.apply {
|
|
||||||
id = R.id.main_playback
|
|
||||||
|
|
||||||
elevation = resources.getDimensionPixelSize(R.dimen.elevation_normal).toFloat()
|
|
||||||
|
|
||||||
|
playbackView.apply {
|
||||||
(layoutParams as LayoutParams).apply {
|
(layoutParams as LayoutParams).apply {
|
||||||
width = ViewGroup.LayoutParams.MATCH_PARENT
|
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
isBar = true
|
isBar = true
|
||||||
}
|
}
|
||||||
|
|
||||||
background = MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
|
||||||
elevation = barLayout.elevation
|
|
||||||
fillColor = ColorStateList.valueOf(R.attr.colorSurface.resolveAttr(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isInEditMode) {
|
|
||||||
(context as AppCompatActivity).supportFragmentManager.apply {
|
|
||||||
this
|
|
||||||
.beginTransaction()
|
|
||||||
.replace(R.id.main_playback, playbackFragment)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,15 +72,15 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
|
|
||||||
setMeasuredDimension(widthSize, heightSize)
|
setMeasuredDimension(widthSize, heightSize)
|
||||||
|
|
||||||
val barParams = barLayout.layoutParams as LayoutParams
|
val barParams = playbackView.layoutParams as LayoutParams
|
||||||
|
|
||||||
val barWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, barParams.width)
|
val barWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, barParams.width)
|
||||||
val barHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, barParams.height)
|
val barHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, barParams.height)
|
||||||
barLayout.measure(barWidthSpec, barHeightSpec)
|
playbackView.measure(barWidthSpec, barHeightSpec)
|
||||||
|
|
||||||
updateWindowInsets()
|
updateWindowInsets()
|
||||||
|
|
||||||
val barHeightAdjusted = (barLayout.measuredHeight * barParams.offset).toInt()
|
val barHeightAdjusted = (playbackView.measuredHeight * barParams.offset).toInt()
|
||||||
|
|
||||||
val contentWidth = measuredWidth
|
val contentWidth = measuredWidth
|
||||||
val contentHeight = measuredHeight - barHeightAdjusted
|
val contentHeight = measuredHeight - barHeightAdjusted
|
||||||
|
@ -129,13 +100,13 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||||
val barHeight = if (barLayout.isVisible) {
|
val barHeight = if (playbackView.isVisible) {
|
||||||
barLayout.measuredHeight
|
playbackView.measuredHeight
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
val barHeightAdjusted = (barHeight * (barLayout.layoutParams as LayoutParams).offset).toInt()
|
val barHeightAdjusted = (barHeight * (playbackView.layoutParams as LayoutParams).offset).toInt()
|
||||||
|
|
||||||
for (child in children) {
|
for (child in children) {
|
||||||
if (child.visibility == View.GONE) continue
|
if (child.visibility == View.GONE) continue
|
||||||
|
@ -154,7 +125,7 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
||||||
barLayout.updatePadding(bottom = insets.systemBarsCompat.bottom)
|
playbackView.updatePadding(bottom = insets.systemBarsCompat.bottom)
|
||||||
|
|
||||||
lastInsets = insets
|
lastInsets = insets
|
||||||
updateWindowInsets()
|
updateWindowInsets()
|
||||||
|
@ -162,6 +133,12 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
return insets
|
return insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
|
||||||
|
playbackView.clearCallback()
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWindowInsets() {
|
private fun updateWindowInsets() {
|
||||||
val insets = lastInsets
|
val insets = lastInsets
|
||||||
|
|
||||||
|
@ -171,8 +148,8 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mutateInsets(insets: WindowInsets): WindowInsets {
|
private fun mutateInsets(insets: WindowInsets): WindowInsets {
|
||||||
val barParams = barLayout.layoutParams as LayoutParams
|
val barParams = playbackView.layoutParams as LayoutParams
|
||||||
val childConsumedInset = (barLayout.measuredHeight * barParams.offset).toInt()
|
val childConsumedInset = (playbackView.measuredHeight * barParams.offset).toInt()
|
||||||
|
|
||||||
val bars = insets.systemBarsCompat
|
val bars = insets.systemBarsCompat
|
||||||
|
|
||||||
|
@ -194,8 +171,29 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
return insets
|
return insets
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showBar() {
|
fun setSong(song: Song?) {
|
||||||
val barParams = barLayout.layoutParams as LayoutParams
|
if (song != null) {
|
||||||
|
showBar()
|
||||||
|
playbackView.setSong(song)
|
||||||
|
} else {
|
||||||
|
hideBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPlaying(isPlaying: Boolean) {
|
||||||
|
playbackView.setPlaying(isPlaying)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPosition(position: Long) {
|
||||||
|
playbackView.setPosition(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setActionCallback(callback: ActionCallback) {
|
||||||
|
playbackView.setCallback(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBar() {
|
||||||
|
val barParams = playbackView.layoutParams as LayoutParams
|
||||||
|
|
||||||
if (barParams.offset == 1f) {
|
if (barParams.offset == 1f) {
|
||||||
return
|
return
|
||||||
|
@ -210,8 +208,8 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideBar() {
|
private fun hideBar() {
|
||||||
val barParams = barLayout.layoutParams as LayoutParams
|
val barParams = playbackView.layoutParams as LayoutParams
|
||||||
|
|
||||||
if (barParams.offset == 0f) {
|
if (barParams.offset == 0f) {
|
||||||
return
|
return
|
||||||
|
@ -260,4 +258,10 @@ class PlaybackBarLayout @JvmOverloads constructor(
|
||||||
|
|
||||||
constructor(source: ViewGroup.LayoutParams) : super(source)
|
constructor(source: ViewGroup.LayoutParams) : super(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ActionCallback {
|
||||||
|
fun onPlayPauseClick()
|
||||||
|
fun onNavToItem()
|
||||||
|
fun onNavToPlayback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -419,7 +419,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
* Restore playback on startup. This can do one of two things:
|
* Restore playback on startup. This can do one of two things:
|
||||||
* - Play a file intent that was given by MainActivity in [playWithUri]
|
* - Play a file intent that was given by MainActivity in [playWithUri]
|
||||||
* - Restore the last playback state if there is no active file intent.
|
* - Restore the last playback state if there is no active file intent.
|
||||||
* TODO: Re-add this to HomeFragment once state can be restored
|
|
||||||
*/
|
*/
|
||||||
fun setupPlayback(context: Context) {
|
fun setupPlayback(context: Context) {
|
||||||
val intentUri = mIntentUri
|
val intentUri = mIntentUri
|
||||||
|
|
|
@ -138,6 +138,10 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
||||||
return color.resolveColor(context)
|
return color.resolveColor(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve window insets in a version-aware manner. This can be used to apply padding to
|
||||||
|
* a view that properly
|
||||||
|
*/
|
||||||
val WindowInsets.systemBarsCompat: Rect get() {
|
val WindowInsets.systemBarsCompat: Rect get() {
|
||||||
return when {
|
return when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||||
|
|
14
app/src/main/res/drawable/ui_shape_ripple.xml
Normal file
14
app/src/main/res/drawable/ui_shape_ripple.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Hack to make MaterialShapeDrawable cooperate with RippleDrawable. See
|
||||||
|
CompactPlaybackView for more info. -->
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="?attr/colorControlHighlight">
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:id="@android:id/background">
|
||||||
|
<shape android:shape="rectangle" />
|
||||||
|
</item>
|
||||||
|
</ripple>
|
|
@ -32,7 +32,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
app:layout_role="content"
|
|
||||||
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_detail" />
|
tools:listitem="@layout/item_detail" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout 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"
|
||||||
tools:context=".playback.CompactPlaybackFragment">
|
tools:context=".playback.CompactPlaybackView">
|
||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
|
@ -10,19 +10,17 @@
|
||||||
name="song"
|
name="song"
|
||||||
type="org.oxycblt.auxio.music.Song" />
|
type="org.oxycblt.auxio.music.Song" />
|
||||||
|
|
||||||
<variable
|
|
||||||
name="playbackModel"
|
|
||||||
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
|
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<merge
|
||||||
android:id="@+id/playback_layout"
|
android:id="@+id/playback_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
android:background="@drawable/ui_background_ripple"
|
android:background="@drawable/ui_background_ripple"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true">
|
android:focusable="true"
|
||||||
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/playback_cover"
|
android:id="@+id/playback_cover"
|
||||||
|
@ -73,7 +71,6 @@
|
||||||
android:src="@drawable/sel_playing_state"
|
android:src="@drawable/sel_playing_state"
|
||||||
android:layout_margin="@dimen/spacing_small"
|
android:layout_margin="@dimen/spacing_small"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
@ -82,12 +79,12 @@
|
||||||
android:id="@+id/playback_progress"
|
android:id="@+id/playback_progress"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/size_stroke_large"
|
android:layout_height="@dimen/size_stroke_large"
|
||||||
|
android:max="@{(int) song.seconds}"
|
||||||
style="@style/Widget.Auxio.ProgressBar"
|
style="@style/Widget.Auxio.ProgressBar"
|
||||||
android:progress="@{playbackModel.positionAsProgress}"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:progress="70" />
|
tools:progress="70" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</merge>
|
||||||
</layout>
|
</layout>
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout 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="@+id/main_playback"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:clipToPadding="true"
|
|
||||||
android:elevation="@dimen/elevation_normal"
|
|
||||||
tools:layout="@layout/fragment_compact_playback" />
|
|
|
@ -4,9 +4,4 @@
|
||||||
<attr name="entries" format="reference" />
|
<attr name="entries" format="reference" />
|
||||||
<attr name="entryValues" format="reference" />
|
<attr name="entryValues" format="reference" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<attr format="enum" name="layout_role">
|
|
||||||
<enum name="content" value="0" />
|
|
||||||
<enum name="floating" value="1" />
|
|
||||||
</attr>
|
|
||||||
</resources>
|
</resources>
|
|
@ -1,5 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<!-- This is for PlaybackBarLayout -->
|
||||||
|
<item name="playback_bar" type="id" />
|
||||||
|
|
||||||
<!-- This is for HomeFragment's AppBarLayout. Explanations for these can be found there. -->
|
<!-- This is for HomeFragment's AppBarLayout. Explanations for these can be found there. -->
|
||||||
<item name="home_song_list" type="id" />
|
<item name="home_song_list" type="id" />
|
||||||
<item name="home_album_list" type="id" />
|
<item name="home_album_list" type="id" />
|
||||||
|
|
Loading…
Reference in a new issue