detail: make album view use collapsing toolbar

This commit is contained in:
Alexander Capehart 2024-07-20 11:19:18 -06:00
parent 82a015c1e1
commit 86e2fd7a89
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 474 additions and 151 deletions

View file

@ -23,14 +23,16 @@ import android.view.LayoutInflater
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.header.AlbumDetailHeaderAdapter
import org.oxycblt.auxio.databinding.FragmentDetail2Binding
import org.oxycblt.auxio.detail.list.AlbumDetailListAdapter
import org.oxycblt.auxio.detail.list.DetailListAdapter
import org.oxycblt.auxio.list.Divider
@ -46,12 +48,14 @@ import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getDimenPixels
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
@ -66,9 +70,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
*/
@AndroidEntryPoint
class AlbumDetailFragment :
ListFragment<Song, FragmentDetailBinding>(),
AlbumDetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Song> {
ListFragment<Song, FragmentDetail2Binding>(),
DetailListAdapter.Listener<Song>,
AppBarLayout.OnOffsetChangedListener {
private val detailModel: DetailViewModel by activityViewModels()
override val listModel: ListViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
@ -77,9 +81,10 @@ class AlbumDetailFragment :
// Information about what album to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an album.
private val args: AlbumDetailFragmentArgs by navArgs()
private val albumHeaderAdapter = AlbumDetailHeaderAdapter(this)
private val albumListAdapter = AlbumDetailListAdapter(this)
private var spacingSmall = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Detail transitions are always on the X axis. Shared element transitions are more
@ -90,15 +95,18 @@ class AlbumDetailFragment :
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
}
override fun onCreateBinding(inflater: LayoutInflater) = FragmentDetailBinding.inflate(inflater)
override fun onCreateBinding(inflater: LayoutInflater) =
FragmentDetail2Binding.inflate(inflater)
override fun getSelectionToolbar(binding: FragmentDetailBinding) =
override fun getSelectionToolbar(binding: FragmentDetail2Binding) =
binding.detailSelectionToolbar
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
override fun onBindingCreated(binding: FragmentDetail2Binding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
// --- UI SETUP --
binding.detailAppbar.addOnOffsetChangedListener(this)
binding.detailNormalToolbar.apply {
setNavigationOnClickListener { findNavController().navigateUp() }
overrideOnOverflowMenuClick {
@ -108,17 +116,23 @@ class AlbumDetailFragment :
}
binding.detailRecycler.apply {
adapter = ConcatAdapter(albumHeaderAdapter, albumListAdapter)
adapter = albumListAdapter
(layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) {
val item = detailModel.albumSongList.value[it - 1]
item is Divider || item is Header || item is Disc
val item =
detailModel.genreSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false
}
item is Divider || item is Header
} else {
true
}
}
}
spacingSmall = requireContext().getDimenPixels(R.dimen.spacing_small)
// -- VIEWMODEL SETUP ---
// DetailViewModel handles most initialization from the navigation argument.
detailModel.setAlbum(args.albumUid)
@ -134,15 +148,31 @@ class AlbumDetailFragment :
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
}
override fun onDestroyBinding(binding: FragmentDetailBinding) {
override fun onDestroyBinding(binding: FragmentDetail2Binding) {
super.onDestroyBinding(binding)
binding.detailNormalToolbar.setOnMenuItemClickListener(null)
binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed
// during list initialization and crash the app. Could happen if the user is fast enough.
detailModel.albumSongInstructions.consume()
}
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
val binding = requireBinding()
val range = appBarLayout.totalScrollRange
val ratio = abs(verticalOffset.toFloat()) / range.toFloat()
val outRatio = min(ratio * 2, 1f)
val detailHeader = binding.detailHeader
detailHeader.scaleX = 1 - 0.05f * outRatio
detailHeader.scaleY = 1 - 0.05f * outRatio
detailHeader.alpha = 1 - outRatio
val inRatio = max(ratio - 0.5f, 0f) * 2
val detailContent = binding.detailToolbarContent
detailContent.alpha = inRatio
detailContent.translationY = spacingSmall * (1 - inRatio)
}
override fun onRealClick(item: Song) {
playbackModel.play(item, detailModel.playInAlbumWith)
}
@ -151,30 +181,50 @@ class AlbumDetailFragment :
listModel.openMenu(R.menu.album_song, item, detailModel.playInAlbumWith)
}
override fun onPlay() {
playbackModel.play(unlikelyToBeNull(detailModel.currentAlbum.value))
}
override fun onShuffle() {
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value))
}
override fun onOpenSortMenu() {
findNavController().navigateSafe(AlbumDetailFragmentDirections.sort())
}
override fun onNavigateToParentArtist() {
detailModel.showArtist(unlikelyToBeNull(detailModel.currentAlbum.value))
}
private fun updateAlbum(album: Album?) {
if (album == null) {
logD("No album to show, navigating away")
findNavController().navigateUp()
return
}
requireBinding().detailNormalToolbar.title = album.name.resolve(requireContext())
albumHeaderAdapter.setParent(album)
val binding = requireBinding()
binding.detailToolbarTitle.text = album.name.resolve(requireContext())
binding.detailCover.bind(album)
// The type text depends on the release type (Album, EP, Single, etc.)
binding.detailType.text = getString(album.releaseType.stringRes)
binding.detailName.text = album.name.resolve(requireContext())
// Artist name maps to the subhead text
binding.detailSubhead.apply {
text = album.artists.resolveNames(context)
// Add a QoL behavior where navigation to the artist will occur if the artist
// name is pressed.
setOnClickListener {
detailModel.showArtist(unlikelyToBeNull(detailModel.currentAlbum.value))
}
}
// Date, song count, and duration map to the info text
binding.detailInfo.apply {
// Fall back to a friendlier "No date" text if the album doesn't have date information
val date = album.dates?.resolveDate(context) ?: context.getString(R.string.def_date)
val songCount = context.getPlural(R.plurals.fmt_song_count, album.songs.size)
val duration = album.durationMs.formatDurationMs(true)
text = context.getString(R.string.fmt_three, date, songCount, duration)
}
binding.detailPlayButton.setOnClickListener {
playbackModel.play(unlikelyToBeNull(detailModel.currentAlbum.value))
}
binding.detailShuffleButton.setOnClickListener {
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value))
}
}
private fun updateList(list: List<Item>) {
@ -318,6 +368,9 @@ class AlbumDetailFragment :
if (pos != -1) {
// Only scroll if the song is within this album.
val binding = requireBinding()
// RecyclerView will scroll assuming it has the total height of the screen (i.e a
// collapsed appbar), so we need to collapse the appbar if that's the case.
binding.detailAppbar.setExpanded(false)
binding.detailRecycler.post {
// Use a custom smooth scroller that will settle the item in the middle of
// the screen rather than the end.
@ -340,11 +393,6 @@ class AlbumDetailFragment :
// Make sure to increment the position to make up for the detail header
binding.detailRecycler.layoutManager?.startSmoothScroll(centerSmoothScroller)
// If the recyclerview can scroll, its certain that it will have to scroll to
// correctly center the playing item, so make sure that the Toolbar is lifted in
// that case.
binding.detailAppbar.isLifted = binding.detailRecycler.canScroll()
}
}
}

View file

@ -556,7 +556,6 @@ constructor(
logD("Refreshing album list")
val list = mutableListOf<Item>()
val header = SortHeader(R.string.lbl_songs)
list.add(Divider(header))
list.add(header)
val instructions =
if (replace) {

View file

@ -1,114 +0,0 @@
/*
* Copyright (c) 2023 Auxio Project
* AlbumDetailHeaderAdapter.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.detail.header
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemDetailHeaderBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater
/**
* A [DetailHeaderAdapter] that shows [Album] information.
*
* @param listener [DetailHeaderAdapter.Listener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt)
*/
class AlbumDetailHeaderAdapter(private val listener: Listener) :
DetailHeaderAdapter<Album, AlbumDetailHeaderViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
AlbumDetailHeaderViewHolder.from(parent)
override fun onBindHeader(holder: AlbumDetailHeaderViewHolder, parent: Album) =
holder.bind(parent, listener)
/** An extended listener for [DetailHeaderAdapter] implementations. */
interface Listener : DetailHeaderAdapter.Listener {
/**
* Called when the artist name in the [Album] header was clicked, requesting navigation to
* it's parent artist.
*/
fun onNavigateToParentArtist()
}
}
/**
* A [RecyclerView.ViewHolder] that displays the [Album] header in the detail view. Use [from] to
* create an instance.
*
* @author Alexander Capehart (OxygenCobalt)
*/
class AlbumDetailHeaderViewHolder
private constructor(private val binding: ItemDetailHeaderBinding) :
RecyclerView.ViewHolder(binding.root) {
/**
* Bind new data to this instance.
*
* @param album The new [Album] to bind.
* @param listener A [AlbumDetailHeaderAdapter.Listener] to bind interactions to.
*/
fun bind(album: Album, listener: AlbumDetailHeaderAdapter.Listener) {
binding.detailCover.bind(album)
// The type text depends on the release type (Album, EP, Single, etc.)
binding.detailType.text = binding.context.getString(album.releaseType.stringRes)
binding.detailName.text = album.name.resolve(binding.context)
// Artist name maps to the subhead text
binding.detailSubhead.apply {
text = album.artists.resolveNames(context)
// Add a QoL behavior where navigation to the artist will occur if the artist
// name is pressed.
setOnClickListener { listener.onNavigateToParentArtist() }
}
// Date, song count, and duration map to the info text
binding.detailInfo.apply {
// Fall back to a friendlier "No date" text if the album doesn't have date information
val date = album.dates?.resolveDate(context) ?: context.getString(R.string.def_date)
val songCount = context.getPlural(R.plurals.fmt_song_count, album.songs.size)
val duration = album.durationMs.formatDurationMs(true)
text = context.getString(R.string.fmt_three, date, songCount, duration)
}
binding.detailPlayButton.setOnClickListener { listener.onPlay() }
binding.detailShuffleButton.setOnClickListener { listener.onShuffle() }
}
companion object {
/**
* Create a new instance.
*
* @param parent The parent to inflate this instance from.
* @return A new instance.
*/
fun from(parent: View) =
AlbumDetailHeaderViewHolder(ItemDetailHeaderBinding.inflate(parent.context.inflater))
}
}

View file

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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/detail_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:transitionGroup="true">
<org.oxycblt.auxio.ui.CoordinatorAppBarLayout
android:id="@+id/detail_appbar"
style="@style/Widget.Auxio.AppBarLayout"
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/detail_recycler">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/detail_collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:titleEnabled="false"
app:toolbarId="@+id/detail_toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/detail_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:paddingStart="@dimen/spacing_medium"
android:paddingTop="86dp"
android:paddingEnd="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_mid_medium"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.85">
<org.oxycblt.auxio.image.CoverView
android:id="@+id/detail_cover"
style="@style/Widget.Auxio.Image.Large"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toTopOf="@+id/detail_play_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/detail_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="@style/TextAppearance.Auxio.LabelMedium"
android:textColor="?attr/colorSecondary"
app:layout_constraintBottom_toTopOf="@+id/detail_name"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toTopOf="@+id/detail_cover"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Type" />
<TextView
android:id="@+id/detail_name"
style="@style/Widget.Auxio.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toTopOf="@+id/detail_subhead"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_type"
tools:text="Name" />
<TextView
android:id="@+id/detail_subhead"
style="@style/Widget.Auxio.TextView.Secondary.Ellipsize"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:clickable="true"
android:focusable="true"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/detail_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_name"
tools:text="Info A" />
<TextView
android:id="@+id/detail_info"
style="@style/Widget.Auxio.TextView.Secondary.Base"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/detail_cover"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_subhead"
tools:text="Info B" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/detail_play_button"
style="@style/Widget.Auxio.Button.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_mid_medium"
android:layout_marginEnd="@dimen/spacing_small"
android:text="@string/lbl_play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_cover" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/detail_shuffle_button"
style="@style/Widget.Auxio.Button.Primary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_small"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_collapseMode="pin" />
<org.oxycblt.auxio.ui.MultiToolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="pin">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_normal_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:menu="@menu/toolbar_detail"
app:navigationIcon="@drawable/ic_back_24">
<LinearLayout
android:id="@+id/detail_toolbar_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/detail_toolbar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_tiny"
android:textAppearance="@style/TextAppearance.Auxio.TitleLarge"
android:textColor="?attr/colorOnSurface"
tools:text="Name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_selection_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:menu="@menu/toolbar_selection"
app:navigationIcon="@drawable/ic_close_24" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_edit_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:menu="@menu/toolbar_edit"
app:navigationIcon="@drawable/ic_close_24" />
</org.oxycblt.auxio.ui.MultiToolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</org.oxycblt.auxio.ui.CoordinatorAppBarLayout>
<org.oxycblt.auxio.list.recycler.AuxioRecyclerView
android:id="@+id/detail_recycler"
style="@style/Widget.Auxio.RecyclerView.Grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_song" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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/detail_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:transitionGroup="true">
<org.oxycblt.auxio.ui.CoordinatorAppBarLayout
android:id="@+id/detail_appbar"
style="@style/Widget.Auxio.AppBarLayout"
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/detail_recycler">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/detail_collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:titleEnabled="false"
app:toolbarId="@+id/detail_toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/detail_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/spacing_medium"
android:paddingTop="86dp"
android:paddingEnd="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_mid_medium"
app:layout_collapseMode="parallax"
android:layout_gravity="bottom"
app:layout_collapseParallaxMultiplier="0.85">
<org.oxycblt.auxio.image.CoverView
android:id="@+id/detail_cover"
style="@style/Widget.Auxio.Image.Huge"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/detail_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_medium"
android:textAppearance="@style/TextAppearance.Auxio.LabelMedium"
android:textColor="?attr/colorSecondary"
app:layout_constraintBottom_toTopOf="@+id/detail_name"
app:layout_constraintTop_toBottomOf="@+id/detail_cover"
tools:layout_editor_absoluteX="16dp"
tools:text="Type" />
<TextView
android:id="@+id/detail_name"
style="@style/Widget.Auxio.TextView.Detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_type"
tools:text="Name" />
<TextView
android:id="@+id/detail_subhead"
style="@style/Widget.Auxio.TextView.Secondary.Ellipsize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_name"
tools:text="Info A" />
<TextView
android:id="@+id/detail_info"
style="@style/Widget.Auxio.TextView.Secondary.Ellipsize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_subhead"
tools:text="Info B" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/detail_play_button"
style="@style/Widget.Auxio.Button.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_mid_medium"
android:layout_marginEnd="@dimen/spacing_small"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_info" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/detail_shuffle_button"
style="@style/Widget.Auxio.Button.Primary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_small"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_collapseMode="pin" />
<org.oxycblt.auxio.ui.MultiToolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="pin" >
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_normal_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:menu="@menu/toolbar_detail"
app:navigationIcon="@drawable/ic_back_24">
<LinearLayout
android:id="@+id/detail_toolbar_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/detail_toolbar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_tiny"
android:textAppearance="@style/TextAppearance.Auxio.TitleLarge"
android:textColor="?attr/colorOnSurface"
tools:text="Name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_selection_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:menu="@menu/toolbar_selection"
app:navigationIcon="@drawable/ic_close_24" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_edit_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:menu="@menu/toolbar_edit"
app:navigationIcon="@drawable/ic_close_24" />
</org.oxycblt.auxio.ui.MultiToolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</org.oxycblt.auxio.ui.CoordinatorAppBarLayout>
<org.oxycblt.auxio.list.recycler.AuxioRecyclerView
android:id="@+id/detail_recycler"
style="@style/Widget.Auxio.RecyclerView.Grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_song" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>