detail: improve disc header design

This commit is contained in:
Alexander Capehart 2024-10-14 18:25:20 -06:00
parent 190abd5588
commit 97faa3f20e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 78 additions and 30 deletions

View file

@ -49,8 +49,10 @@ object IntegerTable {
const val VIEW_TYPE_ARTIST_SONG = 0xA00A const val VIEW_TYPE_ARTIST_SONG = 0xA00A
/** DiscHeaderViewHolder */ /** DiscHeaderViewHolder */
const val VIEW_TYPE_DISC_HEADER = 0xA00B const val VIEW_TYPE_DISC_HEADER = 0xA00B
/** DiscHeaderViewHolder */
const val VIEW_TYPE_DISC_DIVIDER = 0xA00C
/** EditHeaderViewHolder */ /** EditHeaderViewHolder */
const val VIEW_TYPE_EDIT_HEADER = 0xA00C const val VIEW_TYPE_EDIT_HEADER = 0xA00D
/** PlaylistSongViewHolder */ /** PlaylistSongViewHolder */
const val VIEW_TYPE_PLAYLIST_SONG = 0xA00E const val VIEW_TYPE_PLAYLIST_SONG = 0xA00E
/** "Music playback" notification code */ /** "Music playback" notification code */

View file

@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.list.DiscDivider
import org.oxycblt.auxio.detail.list.DiscHeader import org.oxycblt.auxio.detail.list.DiscHeader
import org.oxycblt.auxio.detail.list.EditHeader import org.oxycblt.auxio.detail.list.EditHeader
import org.oxycblt.auxio.detail.list.SortHeader import org.oxycblt.auxio.detail.list.SortHeader
@ -554,7 +555,16 @@ constructor(
newList.add(Divider(header)) newList.add(Divider(header))
} }
newList.add(header) newList.add(header)
section.discs.flatMap { listOf(DiscHeader(it.key)) + it.value } buildList<Item> {
for (entry in section.discs) {
val discHeader = DiscHeader(inner = entry.key)
if (isNotEmpty()) {
add(DiscDivider(discHeader))
}
add(discHeader)
addAll(entry.value)
}
}
} }
} }
// Currently only the final section (songs, which can be sorted) are invalidatable // Currently only the final section (songs, which can be sorted) are invalidatable

View file

@ -24,6 +24,7 @@ import androidx.core.view.isGone
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDivider
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
@ -38,6 +39,7 @@ import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.info.resolveNumber import org.oxycblt.auxio.music.info.resolveNumber
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
/** /**
@ -52,6 +54,7 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
when (getItem(position)) { when (getItem(position)) {
// Support sub-headers for each disc, and special album songs. // Support sub-headers for each disc, and special album songs.
is DiscHeader -> DiscHeaderViewHolder.VIEW_TYPE is DiscHeader -> DiscHeaderViewHolder.VIEW_TYPE
is DiscDivider -> DiscDividerViewHolder.VIEW_TYPE
is Song -> AlbumSongViewHolder.VIEW_TYPE is Song -> AlbumSongViewHolder.VIEW_TYPE
else -> super.getItemViewType(position) else -> super.getItemViewType(position)
} }
@ -59,6 +62,7 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
when (viewType) { when (viewType) {
DiscHeaderViewHolder.VIEW_TYPE -> DiscHeaderViewHolder.from(parent) DiscHeaderViewHolder.VIEW_TYPE -> DiscHeaderViewHolder.from(parent)
DiscDividerViewHolder.VIEW_TYPE -> DiscDividerViewHolder.from(parent)
AlbumSongViewHolder.VIEW_TYPE -> AlbumSongViewHolder.from(parent) AlbumSongViewHolder.VIEW_TYPE -> AlbumSongViewHolder.from(parent)
else -> super.onCreateViewHolder(parent, viewType) else -> super.onCreateViewHolder(parent, viewType)
} }
@ -79,6 +83,8 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
when { when {
oldItem is Disc && newItem is Disc -> oldItem is Disc && newItem is Disc ->
DiscHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) DiscHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is DiscDivider && newItem is DiscDivider ->
DiscDividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is Song && newItem is Song -> oldItem is Song && newItem is Song ->
AlbumSongViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) AlbumSongViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
@ -96,6 +102,8 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
*/ */
data class DiscHeader(val inner: Disc?) : Item data class DiscHeader(val inner: Disc?) : Item
data class DiscDivider(val anchor: DiscHeader?) : Item
/** /**
* A [RecyclerView.ViewHolder] that displays a [DiscHeader] to delimit different disc groups. Use * A [RecyclerView.ViewHolder] that displays a [DiscHeader] to delimit different disc groups. Use
* [from] to create an instance. * [from] to create an instance.
@ -140,6 +148,42 @@ private class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) :
} }
} }
/**
* A [RecyclerView.ViewHolder] that displays a [DiscHeader]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt)
*/
class DiscDividerViewHolder private constructor(divider: MaterialDivider) :
RecyclerView.ViewHolder(divider) {
init {
divider.dividerColor =
divider.context
.getAttrColorCompat(com.google.android.material.R.attr.colorOutlineVariant)
.defaultColor
}
companion object {
/** Unique ID for this ViewHolder type. */
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_DISC_DIVIDER
/**
* Create a new instance.
*
* @param parent The parent to inflate this instance from.
* @return A new instance.
*/
fun from(parent: View) = DiscDividerViewHolder(MaterialDivider(parent.context))
/** A comparator that can be used with DiffUtil. */
val DIFF_CALLBACK =
object : SimpleDiffCallback<DiscDivider>() {
override fun areContentsTheSame(oldItem: DiscDivider, newItem: DiscDivider) =
oldItem.anchor == newItem.anchor
}
}
}
/** /**
* A [RecyclerView.ViewHolder] that displays a [Song] in the context of an [Album]. Use [from] to * A [RecyclerView.ViewHolder] that displays a [Song] in the context of an [Album]. Use [from] to
* create an instance. * create an instance.

View file

@ -6,57 +6,49 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="@dimen/spacing_medium" android:paddingStart="@dimen/spacing_medium"
android:paddingTop="@dimen/spacing_mid_medium" android:paddingTop="@dimen/spacing_small"
android:paddingEnd="@dimen/spacing_medium" android:paddingEnd="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_mid_medium"> android:paddingBottom="@dimen/spacing_small">
<org.oxycblt.auxio.image.CoverView
android:id="@+id/disc_cover"
style="@style/Widget.Auxio.Image.Small"
android:scaleType="matrix"
app:enablePlaybackIndicator="false"
app:enableSelectionBadge="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription">
<ImageView <ImageView
android:id="@+id/disc_icon" android:id="@+id/disc_icon"
android:layout_width="match_parent" android:layout_width="48dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:scaleType="center" android:scaleType="center"
android:src="@drawable/ic_album_24" android:src="@drawable/ic_album_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/sel_on_cover_bg" app:tint="@color/sel_on_cover_bg"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</org.oxycblt.auxio.image.CoverView>
<TextView <TextView
android:id="@+id/disc_number" android:id="@+id/disc_number"
style="@style/Widget.Auxio.TextView.Item.Primary" style="@style/Widget.Auxio.TextView.Primary.Compact"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_mid_medium" android:layout_marginEnd="@dimen/spacing_mid_medium"
android:layout_marginStart="@dimen/spacing_medium"
android:textColor="@color/sel_selectable_text_primary" android:textColor="@color/sel_selectable_text_primary"
app:layout_constraintBottom_toTopOf="@+id/disc_name" app:layout_constraintBottom_toTopOf="@+id/disc_name"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/disc_cover" app:layout_constraintStart_toEndOf="@+id/disc_icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="@+id/disc_icon"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
tools:text="Disc 1" /> tools:text="Disc 1" />
<TextView <TextView
android:id="@+id/disc_name" android:id="@+id/disc_name"
style="@style/Widget.Auxio.TextView.Item.Secondary" style="@style/Widget.Auxio.TextView.Secondary.Compact"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_mid_medium" android:layout_marginEnd="@dimen/spacing_mid_medium"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/disc_icon"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/disc_cover" app:layout_constraintStart_toEndOf="@+id/disc_icon"
app:layout_constraintTop_toBottomOf="@+id/disc_number" app:layout_constraintTop_toBottomOf="@+id/disc_number"
tools:text="Part 1" tools:text="Part 1" />
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>