Revert "playback: temp revert swipe to next behavior"

This reverts commit df7ec27d1c.
This commit is contained in:
Alexander Capehart 2023-11-12 11:05:14 -07:00
parent d5017f8d38
commit 7fef5a27da
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 378 additions and 166 deletions

View file

@ -29,18 +29,26 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import dagger.hilt.android.AndroidEntryPoint
import java.lang.reflect.Field
import kotlin.math.abs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.queue.QueueViewModel
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.ui.PlaybackPagerAdapter
import org.oxycblt.auxio.playback.ui.StyledSeekBar
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
import org.oxycblt.auxio.util.showToast
@ -58,11 +66,14 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
class PlaybackPanelFragment :
ViewBindingFragment<FragmentPlaybackPanelBinding>(),
Toolbar.OnMenuItemClickListener,
StyledSeekBar.Listener {
StyledSeekBar.Listener,
PlaybackPagerAdapter.Listener {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
private val queueModel: QueueViewModel by activityViewModels()
private val listModel: ListViewModel by activityViewModels()
private var equalizerLauncher: ActivityResultLauncher<Intent>? = null
private var coverAdapter: PlaybackPagerAdapter? = null
override fun onCreateBinding(inflater: LayoutInflater) =
FragmentPlaybackPanelBinding.inflate(inflater)
@ -99,19 +110,13 @@ class PlaybackPanelFragment :
}
}
// Set up marquee on song information, alongside click handlers that navigate to each
// respective item.
binding.playbackSong.apply {
isSelected = true
setOnClickListener { playbackModel.song.value?.let(detailModel::showAlbum) }
}
binding.playbackArtist.apply {
isSelected = true
setOnClickListener { navigateToCurrentArtist() }
}
binding.playbackAlbum.apply {
isSelected = true
setOnClickListener { navigateToCurrentAlbum() }
// cover carousel adapter
coverAdapter = PlaybackPagerAdapter(this)
binding.playbackCoverPager.apply {
adapter = coverAdapter
registerOnPageChangeCallback(OnCoverChangedCallback(queueModel))
val recycler = VP_RECYCLER_FIELD.get(this@apply) as RecyclerView
recycler.isNestedScrollingEnabled = false
}
binding.playbackSeekBar.listener = this
@ -131,15 +136,14 @@ class PlaybackPanelFragment :
collectImmediately(playbackModel.repeatMode, ::updateRepeat)
collectImmediately(playbackModel.isPlaying, ::updatePlaying)
collectImmediately(playbackModel.isShuffled, ::updateShuffled)
collectImmediately(queueModel.queue, ::updateQueue)
collectImmediately(queueModel.index, ::updateQueuePosition)
}
override fun onDestroyBinding(binding: FragmentPlaybackPanelBinding) {
equalizerLauncher = null
coverAdapter = null
binding.playbackToolbar.setOnMenuItemClickListener(null)
// Marquee elements leak if they are not disabled when the views are destroyed.
binding.playbackSong.isSelected = false
binding.playbackArtist.isSelected = false
binding.playbackAlbum.isSelected = false
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@ -170,6 +174,18 @@ class PlaybackPanelFragment :
playbackModel.seekTo(positionDs)
}
private fun updateQueue(queue: List<Song>) {
coverAdapter?.update(queue, UpdateInstructions.Replace(0))
}
private fun updateQueuePosition(position: Int) {
val pager = requireBinding().playbackCoverPager
val distance = abs(pager.currentItem - position)
if (distance != 0) {
pager.setCurrentItem(position, distance == 1)
}
}
private fun updateSong(song: Song?) {
if (song == null) {
// Nothing to do.
@ -177,12 +193,7 @@ class PlaybackPanelFragment :
}
val binding = requireBinding()
val context = requireContext()
logD("Updating song display: $song")
binding.playbackCover.bind(song)
binding.playbackSong.text = song.name.resolve(context)
binding.playbackArtist.text = song.artists.resolveNames(context)
binding.playbackAlbum.text = song.album.name.resolve(context)
binding.playbackSeekBar.durationDs = song.durationMs.msToDs()
}
@ -212,11 +223,43 @@ class PlaybackPanelFragment :
requireBinding().playbackShuffle.isActivated = isShuffled
}
private fun navigateToCurrentArtist() {
override fun navigateToCurrentSong() {
playbackModel.song.value?.let(detailModel::showAlbum)
}
override fun navigateToCurrentArtist() {
playbackModel.song.value?.let(detailModel::showArtist)
}
private fun navigateToCurrentAlbum() {
override fun navigateToCurrentAlbum() {
playbackModel.song.value?.let { detailModel.showAlbum(it.album) }
}
override fun navigateToMenu() {
// TODO
}
private class OnCoverChangedCallback(private val queueViewModel: QueueViewModel) :
OnPageChangeCallback() {
private var targetPosition = RecyclerView.NO_POSITION
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
targetPosition = position
}
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state == ViewPager2.SCROLL_STATE_IDLE &&
targetPosition != RecyclerView.NO_POSITION &&
targetPosition != queueViewModel.index.value) {
queueViewModel.goto(targetPosition)
}
}
}
private companion object {
val VP_RECYCLER_FIELD: Field by lazyReflectedField(ViewPager2::class, "mRecyclerView")
}
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2023 Auxio Project
* PlaybackPagerAdapter.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.ui
import android.view.ViewGroup
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import kotlin.jvm.internal.Intrinsics
import org.oxycblt.auxio.databinding.ItemPlaybackSongBinding
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.inflater
/** @author Koitharu, Alexander Capehart (OxygenCobalt) */
class PlaybackPagerAdapter(private val listener: Listener) :
FlexibleListAdapter<Song, CoverViewHolder>(CoverViewHolder.DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoverViewHolder {
return CoverViewHolder.from(parent)
}
override fun onBindViewHolder(holder: CoverViewHolder, position: Int) {
holder.bind(getItem(position), listener)
}
override fun onViewRecycled(holder: CoverViewHolder) {
holder.recycle()
super.onViewRecycled(holder)
}
interface Listener {
fun navigateToCurrentArtist()
fun navigateToCurrentAlbum()
fun navigateToCurrentSong()
fun navigateToMenu()
}
}
class CoverViewHolder private constructor(private val binding: ItemPlaybackSongBinding) :
RecyclerView.ViewHolder(binding.root), DefaultLifecycleObserver {
init {
binding.root.layoutParams =
RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT)
}
/**
* Bind new data to this instance.
*
* @param item The new [Song] to bind.
*/
fun bind(item: Song, listener: PlaybackPagerAdapter.Listener) {
val context = binding.root.context
binding.playbackCover.bind(item)
// binding.playbackCover.bind(item)
binding.playbackSong.apply {
text = item.name.resolve(context)
setOnClickListener { listener.navigateToCurrentSong() }
}
binding.playbackArtist.apply {
text = item.artists.resolveNames(context)
setOnClickListener { listener.navigateToCurrentArtist() }
}
binding.playbackAlbum.apply {
text = item.album.name.resolve(context)
setOnClickListener { listener.navigateToCurrentAlbum() }
}
setSelected(true)
}
fun recycle() {
// Marquee elements leak if they are not disabled when the views are destroyed.
// TODO: Move to TextView impl to avoid having to deal with lifecycle here
setSelected(false)
}
private fun setSelected(value: Boolean) {
binding.playbackSong.isSelected = value
binding.playbackArtist.isSelected = value
binding.playbackAlbum.isSelected = value
}
companion object {
/**
* Create a new instance.
*
* @param parent The parent to inflate this instance from.
* @return A new instance.
*/
fun from(parent: ViewGroup) =
CoverViewHolder(ItemPlaybackSongBinding.inflate(parent.context.inflater))
/** A comparator that can be used with DiffUtil. */
val DIFF_CALLBACK =
object : DiffUtil.ItemCallback<Song>() {
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
oldItem.uid == newItem.uid
override fun areContentsTheSame(oldItem: Song, newItem: Song): Boolean {
return Intrinsics.areEqual(oldItem, newItem)
}
}
}
}

View file

@ -16,54 +16,14 @@
app:title="@string/lbl_playback"
tools:subtitle="@string/lbl_all_songs" />
<org.oxycblt.auxio.image.CoverView
android:id="@+id/playback_cover"
style="@style/Widget.Auxio.Image.Full"
android:layout_margin="@dimen/spacing_medium"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toTopOf="@+id/playback_song"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" />
<TextView
android:id="@+id/playback_song"
style="@style/Widget.Auxio.TextView.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/playback_artist"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_album"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Artist Name" />
<TextView
android:id="@+id/playback_album"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/playback_cover_pager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Album Name" />
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" />
<org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar"

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<org.oxycblt.auxio.image.CoverView
android:id="@+id/playback_cover"
style="@style/Widget.Auxio.Image.Full"
android:layout_margin="@dimen/spacing_medium"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toTopOf="@id/playback_song"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playback_song"
style="@style/Widget.Auxio.TextView.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/playback_artist"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_album"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Artist Name" />
<TextView
android:id="@+id/playback_album"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Album Name" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -16,54 +16,14 @@
app:title="@string/lbl_playback"
tools:subtitle="@string/lbl_all_songs" />
<org.oxycblt.auxio.image.CoverView
android:id="@+id/playback_cover"
style="@style/Widget.Auxio.Image.Full"
android:layout_margin="@dimen/spacing_medium"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toTopOf="@+id/playback_song"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" />
<TextView
android:id="@+id/playback_song"
style="@style/Widget.Auxio.TextView.Primary"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/playback_cover_pager"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/playback_artist"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_album"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Artist Name" />
<TextView
android:id="@+id/playback_album"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Album Name" />
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" />
<org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar"

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<org.oxycblt.auxio.image.CoverView
android:id="@+id/playback_cover"
style="@style/Widget.Auxio.Image.Full"
android:layout_margin="@dimen/spacing_medium"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toTopOf="@id/playback_song"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playback_song"
style="@style/Widget.Auxio.TextView.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/playback_artist"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_album"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Artist Name" />
<TextView
android:id="@+id/playback_album"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Album Name" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -16,64 +16,22 @@
app:title="@string/lbl_playback"
tools:subtitle="@string/lbl_all_songs" />
<org.oxycblt.auxio.image.CoverView
android:id="@+id/playback_cover"
style="@style/Widget.Auxio.Image.Full"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/playback_cover_pager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar"
app:layout_constraintVertical_chainStyle="packed" />
<!-- Playback information is wrapped in a container so that marquee doesn't break -->
<LinearLayout
android:id="@+id/playback_info_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/playback_cover"
app:layout_constraintTop_toTopOf="@+id/playback_cover"
app:layout_constraintVertical_chainStyle="packed">
<TextView
android:id="@+id/playback_song"
style="@style/Widget.Auxio.TextView.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Song Name" />
<TextView
android:id="@+id/playback_artist"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Artist Name" />
<TextView
android:id="@+id/playback_album"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Album Name" />
</LinearLayout>
<org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/playback_controls_container"
app:layout_constraintEnd_toEndOf="@+id/playback_info_container"
app:layout_constraintEnd_toEndOf="@+id/playback_cover_pager"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<org.oxycblt.auxio.image.CoverView
android:id="@+id/playback_cover"
style="@style/Widget.Auxio.Image.Full"
android:layout_margin="@dimen/spacing_medium"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Playback information is wrapped in a container so that marquee doesn't break -->
<LinearLayout
android:id="@+id/playback_info_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/spacing_medium"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/playback_cover"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed">
<TextView
android:id="@+id/playback_song"
style="@style/Widget.Auxio.TextView.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Song Name" />
<TextView
android:id="@+id/playback_artist"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Artist Name" />
<TextView
android:id="@+id/playback_album"
style="@style/Widget.Auxio.TextView.Secondary.Marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Album Name" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>