recycler: unwind abstractions
Unwind the RecyclerView abstractions. The framework was far too suffocating and prevented the addition of new changes. Remove it.
This commit is contained in:
parent
3db68d47a6
commit
9d58076a0a
27 changed files with 524 additions and 718 deletions
|
@ -20,35 +20,31 @@ package org.oxycblt.auxio
|
||||||
/** A table containing all unique integer codes that Auxio uses. */
|
/** A table containing all unique integer codes that Auxio uses. */
|
||||||
object IntegerTable {
|
object IntegerTable {
|
||||||
/** SongViewHolder */
|
/** SongViewHolder */
|
||||||
const val ITEM_TYPE_SONG = 0xA000
|
const val VIEW_TYPE_SONG = 0xA000
|
||||||
/** AlbumViewHolder */
|
/** AlbumViewHolder */
|
||||||
const val ITEM_TYPE_ALBUM = 0xA001
|
const val VIEW_TYPE_ALBUM = 0xA001
|
||||||
/** ArtistViewHolder */
|
/** ArtistViewHolder */
|
||||||
const val ITEM_TYPE_ARTIST = 0xA002
|
const val VIEW_TYPE_ARTIST = 0xA002
|
||||||
/** GenreViewHolder */
|
/** GenreViewHolder */
|
||||||
const val ITEM_TYPE_GENRE = 0xA003
|
const val VIEW_TYPE_GENRE = 0xA003
|
||||||
/** HeaderViewHolder */
|
/** HeaderViewHolder */
|
||||||
const val ITEM_TYPE_HEADER = 0xA004
|
const val VIEW_TYPE_HEADER = 0xA004
|
||||||
/** SortHeaderViewHolder */
|
/** SortHeaderViewHolder */
|
||||||
const val ITEM_TYPE_SORT_HEADER = 0xA005
|
const val VIEW_TYPE_SORT_HEADER = 0xA005
|
||||||
|
|
||||||
/** AlbumDetailViewHolder */
|
/** AlbumDetailViewHolder */
|
||||||
const val ITEM_TYPE_ALBUM_DETAIL = 0xA006
|
const val VIEW_TYPE_ALBUM_DETAIL = 0xA006
|
||||||
/** AlbumSongViewHolder */
|
/** AlbumSongViewHolder */
|
||||||
const val ITEM_TYPE_ALBUM_SONG = 0xA007
|
const val VIEW_TYPE_ALBUM_SONG = 0xA007
|
||||||
/** ArtistDetailViewHolder */
|
/** ArtistDetailViewHolder */
|
||||||
const val ITEM_TYPE_ARTIST_DETAIL = 0xA008
|
const val VIEW_TYPE_ARTIST_DETAIL = 0xA008
|
||||||
/** ArtistAlbumViewHolder */
|
/** ArtistAlbumViewHolder */
|
||||||
const val ITEM_TYPE_ARTIST_ALBUM = 0xA009
|
const val VIEW_TYPE_ARTIST_ALBUM = 0xA009
|
||||||
/** ArtistSongViewHolder */
|
/** ArtistSongViewHolder */
|
||||||
const val ITEM_TYPE_ARTIST_SONG = 0xA00A
|
const val VIEW_TYPE_ARTIST_SONG = 0xA00A
|
||||||
/** GenreDetailViewHolder */
|
/** GenreDetailViewHolder */
|
||||||
const val ITEM_TYPE_GENRE_DETAIL = 0xA00B
|
const val VIEW_TYPE_GENRE_DETAIL = 0xA00B
|
||||||
/** DiscHeaderViewHolder */
|
/** DiscHeaderViewHolder */
|
||||||
const val ITEM_TYPE_DISC_HEADER = 0xA00C
|
const val VIEW_TYPE_DISC_HEADER = 0xA00C
|
||||||
|
|
||||||
/** QueueSongViewHolder */
|
|
||||||
const val ITEM_TYPE_QUEUE_SONG = 0xA00D
|
|
||||||
|
|
||||||
/** "Music playback" notification code */
|
/** "Music playback" notification code */
|
||||||
const val PLAYBACK_NOTIFICATION_CODE = 0xA0A0
|
const val PLAYBACK_NOTIFICATION_CODE = 0xA0A0
|
||||||
|
|
|
@ -88,7 +88,7 @@ class AlbumDetailFragment :
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
setSpanSizeLookup { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailAdapter.data.getItem(pos)
|
val item = detailModel.albumData.value[pos]
|
||||||
item is Album || item is Header || item is SortHeader
|
item is Album || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ class AlbumDetailFragment :
|
||||||
// -- VIEWMODEL SETUP ---
|
// -- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
collectImmediately(detailModel.currentAlbum, ::handleItemChange)
|
collectImmediately(detailModel.currentAlbum, ::handleItemChange)
|
||||||
collectImmediately(detailModel.albumData, detailAdapter.data::submitList)
|
collectImmediately(detailModel.albumData, detailAdapter::submitList)
|
||||||
collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
|
collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
|
||||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||||
}
|
}
|
||||||
|
@ -227,7 +227,7 @@ class AlbumDetailFragment :
|
||||||
/** Scroll to an song using its [id]. */
|
/** Scroll to an song using its [id]. */
|
||||||
private fun scrollToItem(id: Long) {
|
private fun scrollToItem(id: Long) {
|
||||||
// Calculate where the item for the currently played song is
|
// Calculate where the item for the currently played song is
|
||||||
val pos = detailAdapter.data.currentList.indexOfFirst { it.id == id && it is Song }
|
val pos = detailModel.albumData.value.indexOfFirst { it.id == id && it is Song }
|
||||||
|
|
||||||
if (pos != -1) {
|
if (pos != -1) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
|
@ -83,7 +83,7 @@ class ArtistDetailFragment :
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
setSpanSizeLookup { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailAdapter.data.getItem(pos)
|
val item = detailModel.artistData.value[pos]
|
||||||
item is Artist || item is Header || item is SortHeader
|
item is Artist || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class ArtistDetailFragment :
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
collectImmediately(detailModel.currentArtist, ::handleItemChange)
|
collectImmediately(detailModel.currentArtist, ::handleItemChange)
|
||||||
collectImmediately(detailModel.artistData, detailAdapter.data::submitList)
|
collectImmediately(detailModel.artistData, detailAdapter::submitList)
|
||||||
collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
|
collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
|
||||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ class GenreDetailFragment :
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
setSpanSizeLookup { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailAdapter.data.getItem(pos)
|
val item = detailModel.albumData.value[pos]
|
||||||
item is Genre || item is Header || item is SortHeader
|
item is Genre || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class GenreDetailFragment :
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
collectImmediately(detailModel.currentGenre, ::handleItemChange)
|
collectImmediately(detailModel.currentGenre, ::handleItemChange)
|
||||||
collectImmediately(detailModel.genreData, detailAdapter.data::submitList)
|
collectImmediately(detailModel.genreData, detailAdapter::submitList)
|
||||||
collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
|
collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
|
||||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.recycler
|
package org.oxycblt.auxio.detail.recycler
|
||||||
|
|
||||||
import android.content.Context
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
|
@ -28,7 +29,6 @@ import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
|
||||||
import org.oxycblt.auxio.detail.DiscHeader
|
import org.oxycblt.auxio.detail.DiscHeader
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
|
@ -41,42 +41,40 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* An adapter for displaying [Album] information and it's children.
|
* An adapter for displaying [Album] information and it's children.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class AlbumDetailAdapter(listener: Listener) :
|
class AlbumDetailAdapter(private val listener: Listener) :
|
||||||
DetailAdapter<AlbumDetailAdapter.Listener>(listener, DIFFER) {
|
DetailAdapter<AlbumDetailAdapter.Listener>(listener, DIFFER) {
|
||||||
private var currentSong: Song? = null
|
private var currentSong: Song? = null
|
||||||
|
|
||||||
override fun getCreatorFromItem(item: Item) =
|
override fun getItemViewType(position: Int) =
|
||||||
super.getCreatorFromItem(item)
|
when (differ.currentList[position]) {
|
||||||
?: when (item) {
|
is Album -> AlbumDetailViewHolder.VIEW_TYPE
|
||||||
is Album -> AlbumDetailViewHolder.CREATOR
|
is DiscHeader -> DiscHeaderViewHolder.VIEW_TYPE
|
||||||
is DiscHeader -> DiscHeaderViewHolder.CREATOR
|
is Song -> AlbumSongViewHolder.VIEW_TYPE
|
||||||
is Song -> AlbumSongViewHolder.CREATOR
|
else -> super.getItemViewType(position)
|
||||||
else -> null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCreatorFromViewType(viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
super.getCreatorFromViewType(viewType)
|
when (viewType) {
|
||||||
?: when (viewType) {
|
AlbumDetailViewHolder.VIEW_TYPE -> AlbumDetailViewHolder.new(parent)
|
||||||
AlbumDetailViewHolder.CREATOR.viewType -> AlbumDetailViewHolder.CREATOR
|
DiscHeaderViewHolder.VIEW_TYPE -> DiscHeaderViewHolder.new(parent)
|
||||||
DiscHeaderViewHolder.CREATOR.viewType -> DiscHeaderViewHolder.CREATOR
|
AlbumSongViewHolder.VIEW_TYPE -> AlbumSongViewHolder.new(parent)
|
||||||
AlbumSongViewHolder.CREATOR.viewType -> AlbumSongViewHolder.CREATOR
|
else -> super.onCreateViewHolder(parent, viewType)
|
||||||
else -> null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(
|
override fun onBindViewHolder(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
holder: RecyclerView.ViewHolder,
|
||||||
item: Item,
|
position: Int,
|
||||||
listener: Listener,
|
|
||||||
payload: List<Any>
|
payload: List<Any>
|
||||||
) {
|
) {
|
||||||
super.onBind(viewHolder, item, listener, payload)
|
|
||||||
if (payload.isEmpty()) {
|
if (payload.isEmpty()) {
|
||||||
when (item) {
|
when (val item = differ.currentList[position]) {
|
||||||
is Album -> (viewHolder as AlbumDetailViewHolder).bind(item, listener)
|
is Album -> (holder as AlbumDetailViewHolder).bind(item, listener)
|
||||||
is DiscHeader -> (viewHolder as DiscHeaderViewHolder).bind(item, Unit)
|
is DiscHeader -> (holder as DiscHeaderViewHolder).bind(item)
|
||||||
is Song -> (viewHolder as AlbumSongViewHolder).bind(item, listener)
|
is Song -> (holder as AlbumSongViewHolder).bind(item, listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onBindViewHolder(holder, position, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldHighlightViewHolder(item: Item) = item is Song && item.id == currentSong?.id
|
override fun shouldHighlightViewHolder(item: Item) = item is Song && item.id == currentSong?.id
|
||||||
|
@ -111,9 +109,9 @@ class AlbumDetailAdapter(listener: Listener) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AlbumDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
|
private class AlbumDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
|
||||||
BindingViewHolder<Album, AlbumDetailAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Album, listener: AlbumDetailAdapter.Listener) {
|
fun bind(item: Album, listener: AlbumDetailAdapter.Listener) {
|
||||||
binding.detailCover.bind(item)
|
binding.detailCover.bind(item)
|
||||||
binding.detailType.text = binding.context.getString(item.releaseType.stringRes)
|
binding.detailType.text = binding.context.getString(item.releaseType.stringRes)
|
||||||
|
|
||||||
|
@ -141,14 +139,10 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ALBUM_DETAIL
|
||||||
object : Creator<AlbumDetailViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ALBUM_DETAIL
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
AlbumDetailViewHolder(ItemDetailBinding.inflate(context.inflater))
|
AlbumDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Album>() {
|
object : SimpleItemCallback<Album>() {
|
||||||
|
@ -164,21 +158,17 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) :
|
class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) :
|
||||||
BindingViewHolder<DiscHeader, Unit>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: DiscHeader, listener: Unit) {
|
fun bind(item: DiscHeader) {
|
||||||
binding.discNo.text = binding.context.getString(R.string.fmt_disc_no, item.disc)
|
binding.discNo.text = binding.context.getString(R.string.fmt_disc_no, item.disc)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_DISC_HEADER
|
||||||
object : Creator<DiscHeaderViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_DISC_HEADER
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
DiscHeaderViewHolder(ItemDiscHeaderBinding.inflate(context.inflater))
|
DiscHeaderViewHolder(ItemDiscHeaderBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<DiscHeader>() {
|
object : SimpleItemCallback<DiscHeader>() {
|
||||||
|
@ -189,8 +179,8 @@ class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AlbumSongViewHolder private constructor(private val binding: ItemAlbumSongBinding) :
|
private class AlbumSongViewHolder private constructor(private val binding: ItemAlbumSongBinding) :
|
||||||
BindingViewHolder<Song, MenuItemListener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: Song, listener: MenuItemListener) {
|
fun bind(item: Song, listener: MenuItemListener) {
|
||||||
// Hide the track number view if the song does not have a track.
|
// Hide the track number view if the song does not have a track.
|
||||||
if (item.track != null) {
|
if (item.track != null) {
|
||||||
binding.songTrack.apply {
|
binding.songTrack.apply {
|
||||||
|
@ -218,14 +208,10 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ALBUM_SONG
|
||||||
object : Creator<AlbumSongViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ALBUM_SONG
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
AlbumSongViewHolder(ItemAlbumSongBinding.inflate(context.inflater))
|
AlbumSongViewHolder(ItemAlbumSongBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Song>() {
|
object : SimpleItemCallback<Song>() {
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.recycler
|
package org.oxycblt.auxio.detail.recycler
|
||||||
|
|
||||||
import android.content.Context
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
@ -29,7 +30,6 @@ import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.resolveYear
|
import org.oxycblt.auxio.music.resolveYear
|
||||||
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
|
@ -42,44 +42,41 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* one actually contains both album information and song information.
|
* one actually contains both album information and song information.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class ArtistDetailAdapter(listener: Listener) :
|
class ArtistDetailAdapter(private val listener: Listener) :
|
||||||
DetailAdapter<DetailAdapter.Listener>(listener, DIFFER) {
|
DetailAdapter<DetailAdapter.Listener>(listener, DIFFER) {
|
||||||
private var currentAlbum: Album? = null
|
private var currentAlbum: Album? = null
|
||||||
private var currentSong: Song? = null
|
private var currentSong: Song? = null
|
||||||
|
|
||||||
override fun getCreatorFromItem(item: Item) =
|
override fun getItemViewType(position: Int) =
|
||||||
super.getCreatorFromItem(item)
|
when (differ.currentList[position]) {
|
||||||
?: when (item) {
|
is Artist -> ArtistDetailViewHolder.VIEW_TYPE
|
||||||
is Artist -> ArtistDetailViewHolder.CREATOR
|
is Album -> ArtistAlbumViewHolder.VIEW_TYPE
|
||||||
is Album -> ArtistAlbumViewHolder.CREATOR
|
is Song -> ArtistSongViewHolder.VIEW_TYPE
|
||||||
is Song -> ArtistSongViewHolder.CREATOR
|
else -> super.getItemViewType(position)
|
||||||
else -> null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCreatorFromViewType(viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
super.getCreatorFromViewType(viewType)
|
when (viewType) {
|
||||||
?: when (viewType) {
|
ArtistDetailViewHolder.VIEW_TYPE -> ArtistDetailViewHolder.new(parent)
|
||||||
ArtistDetailViewHolder.CREATOR.viewType -> ArtistDetailViewHolder.CREATOR
|
ArtistAlbumViewHolder.VIEW_TYPE -> ArtistAlbumViewHolder.new(parent)
|
||||||
ArtistAlbumViewHolder.CREATOR.viewType -> ArtistAlbumViewHolder.CREATOR
|
ArtistSongViewHolder.VIEW_TYPE -> ArtistSongViewHolder.new(parent)
|
||||||
ArtistSongViewHolder.CREATOR.viewType -> ArtistSongViewHolder.CREATOR
|
else -> super.onCreateViewHolder(parent, viewType)
|
||||||
else -> null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(
|
override fun onBindViewHolder(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
holder: RecyclerView.ViewHolder,
|
||||||
item: Item,
|
position: Int,
|
||||||
listener: Listener,
|
|
||||||
payload: List<Any>
|
payload: List<Any>
|
||||||
) {
|
) {
|
||||||
super.onBind(viewHolder, item, listener, payload)
|
|
||||||
if (payload.isEmpty()) {
|
if (payload.isEmpty()) {
|
||||||
when (item) {
|
when (val item = differ.currentList[position]) {
|
||||||
is Artist -> (viewHolder as ArtistDetailViewHolder).bind(item, listener)
|
is Artist -> (holder as ArtistDetailViewHolder).bind(item, listener)
|
||||||
is Album -> (viewHolder as ArtistAlbumViewHolder).bind(item, listener)
|
is Album -> (holder as ArtistAlbumViewHolder).bind(item, listener)
|
||||||
is Song -> (viewHolder as ArtistSongViewHolder).bind(item, listener)
|
is Song -> (holder as ArtistSongViewHolder).bind(item, listener)
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onBindViewHolder(holder, position, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldHighlightViewHolder(item: Item) =
|
override fun shouldHighlightViewHolder(item: Item) =
|
||||||
|
@ -119,9 +116,9 @@ class ArtistDetailAdapter(listener: Listener) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ArtistDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
|
private class ArtistDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
|
||||||
BindingViewHolder<Artist, DetailAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Artist, listener: DetailAdapter.Listener) {
|
fun bind(item: Artist, listener: DetailAdapter.Listener) {
|
||||||
binding.detailCover.bind(item)
|
binding.detailCover.bind(item)
|
||||||
binding.detailType.text = binding.context.getString(R.string.lbl_artist)
|
binding.detailType.text = binding.context.getString(R.string.lbl_artist)
|
||||||
binding.detailName.text = item.resolveName(binding.context)
|
binding.detailName.text = item.resolveName(binding.context)
|
||||||
|
@ -147,14 +144,10 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ARTIST_DETAIL
|
||||||
object : Creator<ArtistDetailViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ARTIST_DETAIL
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
ArtistDetailViewHolder(ItemDetailBinding.inflate(context.inflater))
|
ArtistDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER = ArtistViewHolder.DIFFER
|
val DIFFER = ArtistViewHolder.DIFFER
|
||||||
}
|
}
|
||||||
|
@ -163,8 +156,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
|
||||||
private class ArtistAlbumViewHolder
|
private class ArtistAlbumViewHolder
|
||||||
private constructor(
|
private constructor(
|
||||||
private val binding: ItemParentBinding,
|
private val binding: ItemParentBinding,
|
||||||
) : BindingViewHolder<Album, MenuItemListener>(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: Album, listener: MenuItemListener) {
|
fun bind(item: Album, listener: MenuItemListener) {
|
||||||
binding.parentImage.bind(item)
|
binding.parentImage.bind(item)
|
||||||
binding.parentName.text = item.resolveName(binding.context)
|
binding.parentName.text = item.resolveName(binding.context)
|
||||||
binding.parentInfo.text = item.date.resolveYear(binding.context)
|
binding.parentInfo.text = item.date.resolveYear(binding.context)
|
||||||
|
@ -177,14 +170,10 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ARTIST_ALBUM
|
||||||
object : Creator<ArtistAlbumViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ARTIST_ALBUM
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
ArtistAlbumViewHolder(ItemParentBinding.inflate(context.inflater))
|
ArtistAlbumViewHolder(ItemParentBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Album>() {
|
object : SimpleItemCallback<Album>() {
|
||||||
|
@ -197,8 +186,8 @@ private constructor(
|
||||||
private class ArtistSongViewHolder
|
private class ArtistSongViewHolder
|
||||||
private constructor(
|
private constructor(
|
||||||
private val binding: ItemSongBinding,
|
private val binding: ItemSongBinding,
|
||||||
) : BindingViewHolder<Song, MenuItemListener>(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: Song, listener: MenuItemListener) {
|
fun bind(item: Song, listener: MenuItemListener) {
|
||||||
binding.songAlbumCover.bind(item)
|
binding.songAlbumCover.bind(item)
|
||||||
binding.songName.text = item.resolveName(binding.context)
|
binding.songName.text = item.resolveName(binding.context)
|
||||||
binding.songInfo.text = item.album.resolveName(binding.context)
|
binding.songInfo.text = item.album.resolveName(binding.context)
|
||||||
|
@ -211,14 +200,10 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ARTIST_SONG
|
||||||
object : Creator<ArtistSongViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ARTIST_SONG
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
ArtistSongViewHolder(ItemSongBinding.inflate(context.inflater))
|
ArtistSongViewHolder(ItemSongBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Song>() {
|
object : SimpleItemCallback<Song>() {
|
||||||
|
|
|
@ -17,35 +17,71 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.recycler
|
package org.oxycblt.auxio.detail.recycler
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
||||||
import org.oxycblt.auxio.detail.SortHeader
|
import org.oxycblt.auxio.detail.SortHeader
|
||||||
import org.oxycblt.auxio.ui.recycler.AsyncBackingData
|
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.Header
|
import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
|
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.MultiAdapter
|
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
|
||||||
abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
||||||
listener: L,
|
private val listener: L,
|
||||||
diffCallback: DiffUtil.ItemCallback<Item>
|
diffCallback: DiffUtil.ItemCallback<Item>
|
||||||
) : MultiAdapter<L>(listener) {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
abstract fun shouldHighlightViewHolder(item: Item): Boolean
|
@Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) =
|
||||||
|
when (differ.currentList[position]) {
|
||||||
|
is Header -> HeaderViewHolder.VIEW_TYPE
|
||||||
|
is SortHeader -> SortHeaderViewHolder.VIEW_TYPE
|
||||||
|
else -> super.getItemViewType(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
when (viewType) {
|
||||||
|
HeaderViewHolder.VIEW_TYPE -> HeaderViewHolder.new(parent)
|
||||||
|
SortHeaderViewHolder.VIEW_TYPE -> SortHeaderViewHolder.new(parent)
|
||||||
|
else -> error("Invalid item type $viewType")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =
|
||||||
|
throw IllegalStateException()
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: RecyclerView.ViewHolder,
|
||||||
|
position: Int,
|
||||||
|
payload: List<Any>
|
||||||
|
) {
|
||||||
|
val item = differ.currentList[position]
|
||||||
|
|
||||||
|
if (payload.isEmpty()) {
|
||||||
|
when (item) {
|
||||||
|
is Header -> (holder as HeaderViewHolder).bind(item)
|
||||||
|
is SortHeader -> (holder as SortHeaderViewHolder).bind(item, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.isActivated = shouldHighlightViewHolder(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val differ = AsyncListDiffer(this, diffCallback)
|
||||||
|
|
||||||
|
protected abstract fun shouldHighlightViewHolder(item: Item): Boolean
|
||||||
|
|
||||||
protected inline fun <reified T : Item> highlightImpl(oldItem: T?, newItem: T?) {
|
protected inline fun <reified T : Item> highlightImpl(oldItem: T?, newItem: T?) {
|
||||||
if (oldItem != null) {
|
if (oldItem != null) {
|
||||||
val pos = data.currentList.indexOfFirst { item -> item.id == oldItem.id && item is T }
|
val pos = differ.currentList.indexOfFirst { item -> item.id == oldItem.id && item is T }
|
||||||
|
|
||||||
if (pos > -1) {
|
if (pos > -1) {
|
||||||
notifyItemChanged(pos, PAYLOAD_HIGHLIGHT_CHANGED)
|
notifyItemChanged(pos, PAYLOAD_HIGHLIGHT_CHANGED)
|
||||||
|
@ -55,7 +91,7 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newItem != null) {
|
if (newItem != null) {
|
||||||
val pos = data.currentList.indexOfFirst { item -> item is T && item.id == newItem.id }
|
val pos = differ.currentList.indexOfFirst { item -> item is T && item.id == newItem.id }
|
||||||
|
|
||||||
if (pos > -1) {
|
if (pos > -1) {
|
||||||
notifyItemChanged(pos, PAYLOAD_HIGHLIGHT_CHANGED)
|
notifyItemChanged(pos, PAYLOAD_HIGHLIGHT_CHANGED)
|
||||||
|
@ -65,36 +101,8 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LeakingThis") override val data = AsyncBackingData(this, diffCallback)
|
fun submitList(list: List<Item>) {
|
||||||
|
differ.submitList(list)
|
||||||
override fun getCreatorFromItem(item: Item) =
|
|
||||||
when (item) {
|
|
||||||
is Header -> HeaderViewHolder.CREATOR
|
|
||||||
is SortHeader -> SortHeaderViewHolder.CREATOR
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCreatorFromViewType(viewType: Int) =
|
|
||||||
when (viewType) {
|
|
||||||
HeaderViewHolder.CREATOR.viewType -> HeaderViewHolder.CREATOR
|
|
||||||
SortHeaderViewHolder.CREATOR.viewType -> SortHeaderViewHolder.CREATOR
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(
|
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
|
||||||
item: Item,
|
|
||||||
listener: L,
|
|
||||||
payload: List<Any>
|
|
||||||
) {
|
|
||||||
if (payload.isEmpty()) {
|
|
||||||
when (item) {
|
|
||||||
is Header -> (viewHolder as HeaderViewHolder).bind(item, Unit)
|
|
||||||
is SortHeader -> (viewHolder as SortHeaderViewHolder).bind(item, listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewHolder.itemView.isActivated = shouldHighlightViewHolder(item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -127,8 +135,8 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
||||||
}
|
}
|
||||||
|
|
||||||
class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
|
class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
|
||||||
BindingViewHolder<SortHeader, DetailAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: SortHeader, listener: DetailAdapter.Listener) {
|
fun bind(item: SortHeader, listener: DetailAdapter.Listener) {
|
||||||
binding.headerTitle.text = binding.context.getString(item.string)
|
binding.headerTitle.text = binding.context.getString(item.string)
|
||||||
binding.headerButton.apply {
|
binding.headerButton.apply {
|
||||||
TooltipCompat.setTooltipText(this, contentDescription)
|
TooltipCompat.setTooltipText(this, contentDescription)
|
||||||
|
@ -137,14 +145,10 @@ class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_SORT_HEADER
|
||||||
object : Creator<SortHeaderViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_SORT_HEADER
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
SortHeaderViewHolder(ItemSortHeaderBinding.inflate(context.inflater))
|
SortHeaderViewHolder(ItemSortHeaderBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<SortHeader>() {
|
object : SimpleItemCallback<SortHeader>() {
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.recycler
|
package org.oxycblt.auxio.detail.recycler
|
||||||
|
|
||||||
import android.content.Context
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
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.ItemDetailBinding
|
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
|
@ -37,40 +37,37 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* An adapter for displaying genre information and it's children.
|
* An adapter for displaying genre information and it's children.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class GenreDetailAdapter(listener: Listener) :
|
class GenreDetailAdapter(private val listener: Listener) :
|
||||||
DetailAdapter<DetailAdapter.Listener>(listener, DIFFER) {
|
DetailAdapter<DetailAdapter.Listener>(listener, DIFFER) {
|
||||||
private var currentSong: Song? = null
|
private var currentSong: Song? = null
|
||||||
|
|
||||||
override fun getCreatorFromItem(item: Item) =
|
override fun getItemViewType(position: Int) =
|
||||||
super.getCreatorFromItem(item)
|
when (differ.currentList[position]) {
|
||||||
?: when (item) {
|
is Genre -> GenreDetailViewHolder.VIEW_TYPE
|
||||||
is Genre -> GenreDetailViewHolder.CREATOR
|
is Song -> SongViewHolder.VIEW_TYPE
|
||||||
is Song -> SongViewHolder.CREATOR
|
else -> super.getItemViewType(position)
|
||||||
else -> null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCreatorFromViewType(viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
super.getCreatorFromViewType(viewType)
|
when (viewType) {
|
||||||
?: when (viewType) {
|
GenreDetailViewHolder.VIEW_TYPE -> GenreDetailViewHolder.new(parent)
|
||||||
GenreDetailViewHolder.CREATOR.viewType -> GenreDetailViewHolder.CREATOR
|
SongViewHolder.VIEW_TYPE -> SongViewHolder.new(parent)
|
||||||
SongViewHolder.CREATOR.viewType -> SongViewHolder.CREATOR
|
else -> super.onCreateViewHolder(parent, viewType)
|
||||||
else -> null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(
|
override fun onBindViewHolder(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
holder: RecyclerView.ViewHolder,
|
||||||
item: Item,
|
position: Int,
|
||||||
listener: Listener,
|
|
||||||
payload: List<Any>
|
payload: List<Any>
|
||||||
) {
|
) {
|
||||||
super.onBind(viewHolder, item, listener, payload)
|
|
||||||
if (payload.isEmpty()) {
|
if (payload.isEmpty()) {
|
||||||
when (item) {
|
when (val item = differ.currentList[position]) {
|
||||||
is Genre -> (viewHolder as GenreDetailViewHolder).bind(item, listener)
|
is Genre -> (holder as GenreDetailViewHolder).bind(item, listener)
|
||||||
is Song -> (viewHolder as SongViewHolder).bind(item, listener)
|
is Song -> (holder as SongViewHolder).bind(item, listener)
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onBindViewHolder(holder, position, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldHighlightViewHolder(item: Item) = item is Song && item.id == currentSong?.id
|
override fun shouldHighlightViewHolder(item: Item) = item is Song && item.id == currentSong?.id
|
||||||
|
@ -99,8 +96,8 @@ class GenreDetailAdapter(listener: Listener) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GenreDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
|
private class GenreDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
|
||||||
BindingViewHolder<Genre, DetailAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: Genre, listener: DetailAdapter.Listener) {
|
fun bind(item: Genre, listener: DetailAdapter.Listener) {
|
||||||
binding.detailCover.bind(item)
|
binding.detailCover.bind(item)
|
||||||
binding.detailType.text = binding.context.getString(R.string.lbl_genre)
|
binding.detailType.text = binding.context.getString(R.string.lbl_genre)
|
||||||
binding.detailName.text = item.resolveName(binding.context)
|
binding.detailName.text = item.resolveName(binding.context)
|
||||||
|
@ -112,14 +109,10 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_GENRE_DETAIL
|
||||||
object : Creator<GenreDetailViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_GENRE_DETAIL
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) =
|
||||||
GenreDetailViewHolder(ItemDetailBinding.inflate(context.inflater))
|
GenreDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Genre>() {
|
object : SimpleItemCallback<Genre>() {
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.oxycblt.auxio.home.list
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
|
@ -30,8 +32,7 @@ import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
|
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncBackingData
|
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.util.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.logEOrThrow
|
import org.oxycblt.auxio.util.logEOrThrow
|
||||||
|
@ -54,7 +55,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
|
||||||
adapter = homeAdapter
|
adapter = homeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
collectImmediately(homeModel.albums, homeAdapter.data::replaceList)
|
collectImmediately(homeModel.albums, homeAdapter::replaceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String? {
|
override fun getPopup(pos: Int): String? {
|
||||||
|
@ -107,9 +108,21 @@ class AlbumListFragment : HomeListFragment<Album>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumAdapter(listener: MenuItemListener) :
|
private class AlbumAdapter(private val listener: MenuItemListener) :
|
||||||
MonoAdapter<Album, MenuItemListener, AlbumViewHolder>(listener) {
|
RecyclerView.Adapter<AlbumViewHolder>() {
|
||||||
override val data = SyncBackingData(this, AlbumViewHolder.DIFFER)
|
private val differ = SyncListDiffer(this, AlbumViewHolder.DIFFER)
|
||||||
override val creator = AlbumViewHolder.CREATOR
|
|
||||||
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
AlbumViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) {
|
||||||
|
holder.bind(differ.currentList[position], listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceList(newList: List<Album>) {
|
||||||
|
differ.replaceList(newList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.oxycblt.auxio.home.list
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -28,8 +30,7 @@ import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncBackingData
|
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.util.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.logEOrThrow
|
import org.oxycblt.auxio.util.logEOrThrow
|
||||||
|
@ -49,7 +50,7 @@ class ArtistListFragment : HomeListFragment<Artist>() {
|
||||||
adapter = homeAdapter
|
adapter = homeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
collectImmediately(homeModel.artists, homeAdapter.data::replaceList)
|
collectImmediately(homeModel.artists, homeAdapter::replaceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String? {
|
override fun getPopup(pos: Int): String? {
|
||||||
|
@ -83,9 +84,21 @@ class ArtistListFragment : HomeListFragment<Artist>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArtistAdapter(listener: MenuItemListener) :
|
private class ArtistAdapter(private val listener: MenuItemListener) :
|
||||||
MonoAdapter<Artist, MenuItemListener, ArtistViewHolder>(listener) {
|
RecyclerView.Adapter<ArtistViewHolder>() {
|
||||||
override val data = SyncBackingData(this, ArtistViewHolder.DIFFER)
|
private val differ = SyncListDiffer(this, ArtistViewHolder.DIFFER)
|
||||||
override val creator = ArtistViewHolder.CREATOR
|
|
||||||
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
ArtistViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ArtistViewHolder, position: Int) {
|
||||||
|
holder.bind(differ.currentList[position], listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceList(newList: List<Artist>) {
|
||||||
|
differ.replaceList(newList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.oxycblt.auxio.home.list
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
@ -28,8 +30,7 @@ import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
|
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncBackingData
|
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.util.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.logEOrThrow
|
import org.oxycblt.auxio.util.logEOrThrow
|
||||||
|
@ -49,7 +50,7 @@ class GenreListFragment : HomeListFragment<Genre>() {
|
||||||
adapter = homeAdapter
|
adapter = homeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
collectImmediately(homeModel.genres, homeAdapter.data::replaceList)
|
collectImmediately(homeModel.genres, homeAdapter::replaceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String? {
|
override fun getPopup(pos: Int): String? {
|
||||||
|
@ -83,9 +84,21 @@ class GenreListFragment : HomeListFragment<Genre>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenreAdapter(listener: MenuItemListener) :
|
private class GenreAdapter(private val listener: MenuItemListener) :
|
||||||
MonoAdapter<Genre, MenuItemListener, GenreViewHolder>(listener) {
|
RecyclerView.Adapter<GenreViewHolder>() {
|
||||||
override val data = SyncBackingData(this, GenreViewHolder.DIFFER)
|
private val differ = SyncListDiffer(this, GenreViewHolder.DIFFER)
|
||||||
override val creator = GenreViewHolder.CREATOR
|
|
||||||
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
GenreViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
|
||||||
|
holder.bind(differ.currentList[position], listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceList(newList: List<Genre>) {
|
||||||
|
differ.replaceList(newList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.oxycblt.auxio.home.list
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import java.util.Formatter
|
import java.util.Formatter
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
|
@ -29,9 +31,8 @@ import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
|
||||||
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncBackingData
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.util.formatDurationMs
|
||||||
|
@ -43,7 +44,7 @@ import org.oxycblt.auxio.util.secsToMs
|
||||||
* @author
|
* @author
|
||||||
*/
|
*/
|
||||||
class SongListFragment : HomeListFragment<Song>() {
|
class SongListFragment : HomeListFragment<Song>() {
|
||||||
private val homeAdapter = SongsAdapter(this)
|
private val homeAdapter = SongAdapter(this)
|
||||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||||
private val formatterSb = StringBuilder(50)
|
private val formatterSb = StringBuilder(50)
|
||||||
private val formatter = Formatter(formatterSb)
|
private val formatter = Formatter(formatterSb)
|
||||||
|
@ -56,7 +57,7 @@ class SongListFragment : HomeListFragment<Song>() {
|
||||||
adapter = homeAdapter
|
adapter = homeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
collectImmediately(homeModel.songs, homeAdapter.data::replaceList)
|
collectImmediately(homeModel.songs, homeAdapter::replaceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String? {
|
override fun getPopup(pos: Int): String? {
|
||||||
|
@ -111,9 +112,21 @@ class SongListFragment : HomeListFragment<Song>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class SongsAdapter(listener: MenuItemListener) :
|
private class SongAdapter(private val listener: MenuItemListener) :
|
||||||
MonoAdapter<Song, MenuItemListener, SongViewHolder>(listener) {
|
RecyclerView.Adapter<SongViewHolder>() {
|
||||||
override val data = SyncBackingData(this, SongViewHolder.DIFFER)
|
private val differ = SyncListDiffer(this, SongViewHolder.DIFFER)
|
||||||
override val creator = SongViewHolder.CREATOR
|
|
||||||
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
SongViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
|
||||||
|
holder.bind(differ.currentList[position], listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceList(newList: List<Song>) {
|
||||||
|
differ.replaceList(newList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,63 +18,65 @@
|
||||||
package org.oxycblt.auxio.home.tabs
|
package org.oxycblt.auxio.home.tabs
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.ItemTabBinding
|
import org.oxycblt.auxio.databinding.ItemTabBinding
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.recycler.BackingData
|
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
class TabAdapter(listener: Listener) :
|
class TabAdapter(private val listener: Listener) : RecyclerView.Adapter<TabViewHolder>() {
|
||||||
MonoAdapter<Tab, TabAdapter.Listener, TabViewHolder>(listener) {
|
var tabs = arrayOf<Tab>()
|
||||||
override val data = TabData(this)
|
private set
|
||||||
override val creator = TabViewHolder.CREATOR
|
|
||||||
|
override fun getItemCount() = tabs.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = TabViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
|
||||||
|
holder.bind(tabs[position], listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NotifyDatasetChanged")
|
||||||
|
fun submitTabs(newTabs: Array<Tab>) {
|
||||||
|
tabs = newTabs
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTab(at: Int, tab: Tab) {
|
||||||
|
tabs[at] = tab
|
||||||
|
notifyItemChanged(at, PAYLOAD_TAB_CHANGED)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveItems(from: Int, to: Int) {
|
||||||
|
val t = tabs[to]
|
||||||
|
val f = tabs[from]
|
||||||
|
tabs[from] = t
|
||||||
|
tabs[to] = f
|
||||||
|
notifyItemMoved(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onVisibilityToggled(displayMode: DisplayMode)
|
fun onVisibilityToggled(displayMode: DisplayMode)
|
||||||
fun onPickUpTab(viewHolder: RecyclerView.ViewHolder)
|
fun onPickUpTab(viewHolder: RecyclerView.ViewHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabData(private val adapter: RecyclerView.Adapter<*>) : BackingData<Tab>() {
|
|
||||||
var tabs = arrayOf<Tab>()
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun getItem(position: Int) = tabs[position]
|
|
||||||
override fun getItemCount() = tabs.size
|
|
||||||
|
|
||||||
@Suppress("NotifyDatasetChanged")
|
|
||||||
fun submitTabs(newTabs: Array<Tab>) {
|
|
||||||
tabs = newTabs
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTab(at: Int, tab: Tab) {
|
|
||||||
tabs[at] = tab
|
|
||||||
adapter.notifyItemChanged(at, PAYLOAD_TAB_CHANGED)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun moveItems(from: Int, to: Int) {
|
|
||||||
val t = tabs[to]
|
|
||||||
val f = tabs[from]
|
|
||||||
tabs[from] = t
|
|
||||||
tabs[to] = f
|
|
||||||
adapter.notifyItemMoved(from, to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val PAYLOAD_TAB_CHANGED = Any()
|
val PAYLOAD_TAB_CHANGED = Any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabViewHolder private constructor(private val binding: ItemTabBinding) :
|
class TabViewHolder private constructor(private val binding: ItemTabBinding) :
|
||||||
BindingViewHolder<Tab, TabAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun bind(item: Tab, listener: TabAdapter.Listener) {
|
fun bind(item: Tab, listener: TabAdapter.Listener) {
|
||||||
binding.root.apply { setOnClickListener { listener.onVisibilityToggled(item.mode) } }
|
// Actually make the item full-width, which it won't be in dialogs
|
||||||
|
binding.root.layoutParams =
|
||||||
|
RecyclerView.LayoutParams(
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { listener.onVisibilityToggled(item.mode) }
|
||||||
|
|
||||||
binding.tabIcon.apply {
|
binding.tabIcon.apply {
|
||||||
setText(item.mode.string)
|
setText(item.mode.string)
|
||||||
|
@ -92,13 +94,6 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
fun new(parent: View) = TabViewHolder(ItemTabBinding.inflate(parent.context.inflater))
|
||||||
object : Creator<TabViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
|
||||||
TabViewHolder(ItemTabBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
||||||
.setTitle(R.string.set_lib_tabs)
|
.setTitle(R.string.set_lib_tabs)
|
||||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||||
logD("Committing tab changes")
|
logD("Committing tab changes")
|
||||||
settings.libTabs = tabAdapter.data.tabs
|
settings.libTabs = tabAdapter.tabs
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
}
|
}
|
||||||
|
@ -58,9 +58,9 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
||||||
val savedTabs = findSavedTabState(savedInstanceState)
|
val savedTabs = findSavedTabState(savedInstanceState)
|
||||||
if (savedTabs != null) {
|
if (savedTabs != null) {
|
||||||
logD("Found saved tab state")
|
logD("Found saved tab state")
|
||||||
tabAdapter.data.submitTabs(savedTabs)
|
tabAdapter.submitTabs(savedTabs)
|
||||||
} else {
|
} else {
|
||||||
tabAdapter.data.submitTabs(settings.libTabs)
|
tabAdapter.submitTabs(settings.libTabs)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.tabRecycler.apply {
|
binding.tabRecycler.apply {
|
||||||
|
@ -71,7 +71,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.putInt(KEY_TABS, Tab.toSequence(tabAdapter.data.tabs))
|
outState.putInt(KEY_TABS, Tab.toSequence(tabAdapter.tabs))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyBinding(binding: DialogTabsBinding) {
|
override fun onDestroyBinding(binding: DialogTabsBinding) {
|
||||||
|
@ -83,10 +83,10 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
||||||
// Tab viewholders bind with the initial tab state, which will drift from the actual
|
// Tab viewholders bind with the initial tab state, which will drift from the actual
|
||||||
// state of the tabs over editing. So, this callback simply provides the displayMode
|
// state of the tabs over editing. So, this callback simply provides the displayMode
|
||||||
// for us to locate within the data and then update.
|
// for us to locate within the data and then update.
|
||||||
val index = tabAdapter.data.tabs.indexOfFirst { it.mode == displayMode }
|
val index = tabAdapter.tabs.indexOfFirst { it.mode == displayMode }
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
val tab = tabAdapter.data.tabs[index]
|
val tab = tabAdapter.tabs[index]
|
||||||
tabAdapter.data.setTab(
|
tabAdapter.setTab(
|
||||||
index,
|
index,
|
||||||
when (tab) {
|
when (tab) {
|
||||||
is Tab.Visible -> Tab.Invisible(tab.mode)
|
is Tab.Visible -> Tab.Invisible(tab.mode)
|
||||||
|
@ -95,7 +95,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
||||||
}
|
}
|
||||||
|
|
||||||
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||||
tabAdapter.data.tabs.filterIsInstance<Tab.Visible>().isNotEmpty()
|
tabAdapter.tabs.filterIsInstance<Tab.Visible>().isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPickUpTab(viewHolder: RecyclerView.ViewHolder) {
|
override fun onPickUpTab(viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
|
|
@ -56,7 +56,7 @@ class TabDragCallback(private val adapter: TabAdapter) : ItemTouchHelper.Callbac
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
): Boolean {
|
): Boolean {
|
||||||
adapter.data.moveItems(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
|
adapter.moveItems(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,74 +17,71 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.dirs
|
package org.oxycblt.auxio.music.dirs
|
||||||
|
|
||||||
import android.content.Context
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.ItemMusicDirBinding
|
import org.oxycblt.auxio.databinding.ItemMusicDirBinding
|
||||||
import org.oxycblt.auxio.music.Directory
|
import org.oxycblt.auxio.music.Directory
|
||||||
import org.oxycblt.auxio.ui.recycler.BackingData
|
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter that shows the excluded directories and their "Clear" button.
|
* Adapter that shows the list of music folder and their "Clear" button.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class MusicDirAdapter(listener: Listener) :
|
class MusicDirAdapter(private val listener: Listener) : RecyclerView.Adapter<MusicDirViewHolder>() {
|
||||||
MonoAdapter<Directory, MusicDirAdapter.Listener, MusicDirViewHolder>(listener) {
|
private val _dirs = mutableListOf<Directory>()
|
||||||
override val data = ExcludedBackingData(this)
|
val dirs: List<Directory> = _dirs
|
||||||
override val creator = MusicDirViewHolder.CREATOR
|
|
||||||
|
override fun getItemCount() = dirs.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
MusicDirViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: MusicDirViewHolder, position: Int) =
|
||||||
|
holder.bind(dirs[position], listener)
|
||||||
|
|
||||||
|
fun add(dir: Directory) {
|
||||||
|
if (_dirs.contains(dir)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_dirs.add(dir)
|
||||||
|
notifyItemInserted(_dirs.lastIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAll(dirs: List<Directory>) {
|
||||||
|
val oldLastIndex = dirs.lastIndex
|
||||||
|
_dirs.addAll(dirs)
|
||||||
|
notifyItemRangeInserted(oldLastIndex, dirs.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(dir: Directory) {
|
||||||
|
val idx = _dirs.indexOf(dir)
|
||||||
|
_dirs.removeAt(idx)
|
||||||
|
notifyItemRemoved(idx)
|
||||||
|
}
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onRemoveDirectory(dir: Directory)
|
fun onRemoveDirectory(dir: Directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExcludedBackingData(private val adapter: MusicDirAdapter) : BackingData<Directory>() {
|
|
||||||
private val _currentList = mutableListOf<Directory>()
|
|
||||||
val currentList: List<Directory> = _currentList
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = _currentList.size
|
|
||||||
override fun getItem(position: Int): Directory = _currentList[position]
|
|
||||||
|
|
||||||
fun add(dir: Directory) {
|
|
||||||
if (_currentList.contains(dir)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentList.add(dir)
|
|
||||||
adapter.notifyItemInserted(_currentList.lastIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addAll(dirs: List<Directory>) {
|
|
||||||
val oldLastIndex = dirs.lastIndex
|
|
||||||
_currentList.addAll(dirs)
|
|
||||||
adapter.notifyItemRangeInserted(oldLastIndex, dirs.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(dir: Directory) {
|
|
||||||
val idx = _currentList.indexOf(dir)
|
|
||||||
_currentList.removeAt(idx)
|
|
||||||
adapter.notifyItemRemoved(idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The viewholder for [MusicDirAdapter]. Not intended for use in other adapters. */
|
/** The viewholder for [MusicDirAdapter]. Not intended for use in other adapters. */
|
||||||
class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBinding) :
|
class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBinding) :
|
||||||
BindingViewHolder<Directory, MusicDirAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: Directory, listener: MusicDirAdapter.Listener) {
|
fun bind(item: Directory, listener: MusicDirAdapter.Listener) {
|
||||||
|
// Actually make the item full-width, which it won't be in dialogs
|
||||||
|
binding.root.layoutParams =
|
||||||
|
RecyclerView.LayoutParams(
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
binding.dirPath.text = item.resolveName(binding.context)
|
binding.dirPath.text = item.resolveName(binding.context)
|
||||||
binding.dirDelete.setOnClickListener { listener.onRemoveDirectory(item) }
|
binding.dirDelete.setOnClickListener { listener.onRemoveDirectory(item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
fun new(parent: View) =
|
||||||
object : Creator<MusicDirViewHolder> {
|
MusicDirViewHolder(ItemMusicDirBinding.inflate(parent.context.inflater))
|
||||||
override val viewType: Int
|
|
||||||
get() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
|
||||||
MusicDirViewHolder(ItemMusicDirBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,9 +60,7 @@ class MusicDirsDialog :
|
||||||
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
||||||
val dirs = settings.getMusicDirs(storageManager)
|
val dirs = settings.getMusicDirs(storageManager)
|
||||||
val newDirs =
|
val newDirs =
|
||||||
MusicDirs(
|
MusicDirs(dirs = dirAdapter.dirs, shouldInclude = isInclude(requireBinding()))
|
||||||
dirs = dirAdapter.data.currentList,
|
|
||||||
shouldInclude = isInclude(requireBinding()))
|
|
||||||
if (dirs != newDirs) {
|
if (dirs != newDirs) {
|
||||||
logD("Committing changes")
|
logD("Committing changes")
|
||||||
settings.setMusicDirs(newDirs)
|
settings.setMusicDirs(newDirs)
|
||||||
|
@ -105,7 +103,7 @@ class MusicDirsDialog :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dirAdapter.data.addAll(dirs.dirs)
|
dirAdapter.addAll(dirs.dirs)
|
||||||
requireBinding().dirsEmpty.isVisible = dirs.dirs.isEmpty()
|
requireBinding().dirsEmpty.isVisible = dirs.dirs.isEmpty()
|
||||||
|
|
||||||
binding.folderModeGroup.apply {
|
binding.folderModeGroup.apply {
|
||||||
|
@ -124,7 +122,7 @@ class MusicDirsDialog :
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.putStringArrayList(
|
outState.putStringArrayList(
|
||||||
KEY_PENDING_DIRS, ArrayList(dirAdapter.data.currentList.map { it.toString() }))
|
KEY_PENDING_DIRS, ArrayList(dirAdapter.dirs.map { it.toString() }))
|
||||||
outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding()))
|
outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +132,8 @@ class MusicDirsDialog :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemoveDirectory(dir: Directory) {
|
override fun onRemoveDirectory(dir: Directory) {
|
||||||
dirAdapter.data.remove(dir)
|
dirAdapter.remove(dir)
|
||||||
requireBinding().dirsEmpty.isVisible = dirAdapter.data.currentList.isEmpty()
|
requireBinding().dirsEmpty.isVisible = dirAdapter.dirs.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addDocTreePath(uri: Uri?) {
|
private fun addDocTreePath(uri: Uri?) {
|
||||||
|
@ -147,7 +145,7 @@ class MusicDirsDialog :
|
||||||
|
|
||||||
val dir = parseExcludedUri(uri)
|
val dir = parseExcludedUri(uri)
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
dirAdapter.data.add(dir)
|
dirAdapter.add(dir)
|
||||||
requireBinding().dirsEmpty.isVisible = false
|
requireBinding().dirsEmpty.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
requireContext().showToast(R.string.err_bad_dir)
|
requireContext().showToast(R.string.err_bad_dir)
|
||||||
|
|
|
@ -18,26 +18,31 @@
|
||||||
package org.oxycblt.auxio.playback.queue
|
package org.oxycblt.auxio.playback.queue
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import org.oxycblt.auxio.IntegerTable
|
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.recycler.*
|
import org.oxycblt.auxio.ui.recycler.*
|
||||||
import org.oxycblt.auxio.util.*
|
import org.oxycblt.auxio.util.*
|
||||||
|
|
||||||
class QueueAdapter(listener: QueueItemListener) :
|
class QueueAdapter(private val listener: QueueItemListener) :
|
||||||
MonoAdapter<Song, QueueItemListener, QueueSongViewHolder>(listener) {
|
RecyclerView.Adapter<QueueSongViewHolder>() {
|
||||||
|
private var differ = SyncListDiffer(this, QueueSongViewHolder.DIFFER)
|
||||||
private var currentIndex = 0
|
private var currentIndex = 0
|
||||||
|
|
||||||
override val data = SyncBackingData(this, QueueSongViewHolder.DIFFER)
|
override fun getItemCount() = differ.currentList.size
|
||||||
override val creator = QueueSongViewHolder.CREATOR
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
QueueSongViewHolder.new(parent)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: QueueSongViewHolder, position: Int) =
|
||||||
|
throw IllegalStateException()
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
override fun onBindViewHolder(
|
||||||
viewHolder: QueueSongViewHolder,
|
viewHolder: QueueSongViewHolder,
|
||||||
|
@ -45,13 +50,21 @@ class QueueAdapter(listener: QueueItemListener) :
|
||||||
payload: List<Any>
|
payload: List<Any>
|
||||||
) {
|
) {
|
||||||
if (payload.isEmpty()) {
|
if (payload.isEmpty()) {
|
||||||
super.onBindViewHolder(viewHolder, position, payload)
|
viewHolder.bind(differ.currentList[position], listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewHolder.isEnabled = position > currentIndex
|
viewHolder.isEnabled = position > currentIndex
|
||||||
viewHolder.isActivated = position == currentIndex
|
viewHolder.isActivated = position == currentIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun submitList(newList: List<Song>) {
|
||||||
|
differ.submitList(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceList(newList: List<Song>) {
|
||||||
|
differ.replaceList(newList)
|
||||||
|
}
|
||||||
|
|
||||||
fun updateIndex(index: Int) {
|
fun updateIndex(index: Int) {
|
||||||
when {
|
when {
|
||||||
index < currentIndex -> {
|
index < currentIndex -> {
|
||||||
|
@ -79,7 +92,7 @@ interface QueueItemListener {
|
||||||
class QueueSongViewHolder
|
class QueueSongViewHolder
|
||||||
private constructor(
|
private constructor(
|
||||||
private val binding: ItemQueueSongBinding,
|
private val binding: ItemQueueSongBinding,
|
||||||
) : BindingViewHolder<Song, QueueItemListener>(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
val bodyView: View
|
val bodyView: View
|
||||||
get() = binding.body
|
get() = binding.body
|
||||||
val backgroundView: View
|
val backgroundView: View
|
||||||
|
@ -92,23 +105,6 @@ private constructor(
|
||||||
alpha = 0
|
alpha = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var isEnabled: Boolean
|
|
||||||
get() = binding.songAlbumCover.isEnabled
|
|
||||||
set(value) {
|
|
||||||
// Don't want to disable clicking, just indicate the body and handle is disabled
|
|
||||||
binding.songAlbumCover.isEnabled = value
|
|
||||||
binding.songName.isEnabled = value
|
|
||||||
binding.songInfo.isEnabled = value
|
|
||||||
binding.songDragHandle.isEnabled = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var isActivated: Boolean
|
|
||||||
get() = binding.interactBody.isActivated
|
|
||||||
set(value) {
|
|
||||||
// Activation does not affect clicking, make everything activated.
|
|
||||||
binding.interactBody.isActivated = value
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.body.background =
|
binding.body.background =
|
||||||
LayerDrawable(
|
LayerDrawable(
|
||||||
|
@ -121,7 +117,7 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun bind(item: Song, listener: QueueItemListener) {
|
fun bind(item: Song, listener: QueueItemListener) {
|
||||||
binding.songAlbumCover.bind(item)
|
binding.songAlbumCover.bind(item)
|
||||||
binding.songName.text = item.resolveName(binding.context)
|
binding.songName.text = item.resolveName(binding.context)
|
||||||
binding.songInfo.text = item.resolveIndividualArtistName(binding.context)
|
binding.songInfo.text = item.resolveIndividualArtistName(binding.context)
|
||||||
|
@ -140,15 +136,26 @@ private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
var isEnabled: Boolean
|
||||||
val CREATOR =
|
get() = binding.songAlbumCover.isEnabled
|
||||||
object : Creator<QueueSongViewHolder> {
|
set(value) {
|
||||||
override val viewType: Int
|
// Don't want to disable clicking, just indicate the body and handle is disabled
|
||||||
get() = IntegerTable.ITEM_TYPE_QUEUE_SONG
|
binding.songAlbumCover.isEnabled = value
|
||||||
|
binding.songName.isEnabled = value
|
||||||
|
binding.songInfo.isEnabled = value
|
||||||
|
binding.songDragHandle.isEnabled = value
|
||||||
|
}
|
||||||
|
|
||||||
override fun create(context: Context): QueueSongViewHolder =
|
var isActivated: Boolean
|
||||||
QueueSongViewHolder(ItemQueueSongBinding.inflate(context.inflater))
|
get() = binding.interactBody.isActivated
|
||||||
}
|
set(value) {
|
||||||
|
// Activation does not affect clicking, make everything activated.
|
||||||
|
binding.interactBody.isActivated = value
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun new(parent: View) =
|
||||||
|
QueueSongViewHolder(ItemQueueSongBinding.inflate(parent.context.inflater))
|
||||||
|
|
||||||
val DIFFER = SongViewHolder.DIFFER
|
val DIFFER = SongViewHolder.DIFFER
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,10 +85,10 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
||||||
val replaceQueue = queueModel.replaceQueue
|
val replaceQueue = queueModel.replaceQueue
|
||||||
if (replaceQueue == true) {
|
if (replaceQueue == true) {
|
||||||
logD("Replacing queue")
|
logD("Replacing queue")
|
||||||
queueAdapter.data.replaceList(queue)
|
queueAdapter.replaceList(queue)
|
||||||
} else {
|
} else {
|
||||||
logD("Diffing queue")
|
logD("Diffing queue")
|
||||||
queueAdapter.data.submitList(queue)
|
queueAdapter.submitList(queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.queueDivider.isInvisible =
|
binding.queueDivider.isInvisible =
|
||||||
|
|
|
@ -200,6 +200,7 @@ class PlaybackService :
|
||||||
playbackManager.isPlaying = false
|
playbackManager.isPlaying = false
|
||||||
|
|
||||||
playbackManager.unregisterInternalPlayer(this)
|
playbackManager.unregisterInternalPlayer(this)
|
||||||
|
musicStore.addCallback(this)
|
||||||
settings.release()
|
settings.release()
|
||||||
unregisterReceiver(systemReceiver)
|
unregisterReceiver(systemReceiver)
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.search
|
package org.oxycblt.auxio.search
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -24,55 +26,52 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
|
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.AsyncBackingData
|
|
||||||
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
|
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.Header
|
import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
|
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.MultiAdapter
|
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
|
|
||||||
class SearchAdapter(listener: MenuItemListener) : MultiAdapter<MenuItemListener>(listener) {
|
class SearchAdapter(private val listener: MenuItemListener) :
|
||||||
override val data = AsyncBackingData(this, DIFFER)
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
private val differ = AsyncListDiffer(this, DIFFER)
|
||||||
|
|
||||||
override fun getCreatorFromItem(item: Item) =
|
override fun getItemCount() = differ.currentList.size
|
||||||
when (item) {
|
|
||||||
is Song -> SongViewHolder.CREATOR
|
override fun getItemViewType(position: Int) =
|
||||||
is Album -> AlbumViewHolder.CREATOR
|
when (differ.currentList[position]) {
|
||||||
is Artist -> ArtistViewHolder.CREATOR
|
is Song -> SongViewHolder.VIEW_TYPE
|
||||||
is Genre -> GenreViewHolder.CREATOR
|
is Album -> AlbumViewHolder.VIEW_TYPE
|
||||||
is Header -> HeaderViewHolder.CREATOR
|
is Artist -> ArtistViewHolder.VIEW_TYPE
|
||||||
else -> null
|
is Genre -> HeaderViewHolder.VIEW_TYPE
|
||||||
|
is Header -> HeaderViewHolder.VIEW_TYPE
|
||||||
|
else -> super.getItemViewType(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCreatorFromViewType(viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
when (viewType) {
|
when (viewType) {
|
||||||
SongViewHolder.CREATOR.viewType -> SongViewHolder.CREATOR
|
SongViewHolder.VIEW_TYPE -> SongViewHolder.new(parent)
|
||||||
AlbumViewHolder.CREATOR.viewType -> AlbumViewHolder.CREATOR
|
AlbumViewHolder.VIEW_TYPE -> AlbumViewHolder.new(parent)
|
||||||
ArtistViewHolder.CREATOR.viewType -> ArtistViewHolder.CREATOR
|
ArtistViewHolder.VIEW_TYPE -> ArtistViewHolder.new(parent)
|
||||||
GenreViewHolder.CREATOR.viewType -> GenreViewHolder.CREATOR
|
GenreViewHolder.VIEW_TYPE -> GenreViewHolder.new(parent)
|
||||||
HeaderViewHolder.CREATOR.viewType -> HeaderViewHolder.CREATOR
|
HeaderViewHolder.VIEW_TYPE -> HeaderViewHolder.new(parent)
|
||||||
else -> null
|
else -> error("Invalid item type $viewType")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
when (val item = differ.currentList[position]) {
|
||||||
item: Item,
|
is Song -> (holder as SongViewHolder).bind(item, listener)
|
||||||
listener: MenuItemListener,
|
is Album -> (holder as AlbumViewHolder).bind(item, listener)
|
||||||
payload: List<Any>
|
is Artist -> (holder as ArtistViewHolder).bind(item, listener)
|
||||||
) {
|
is Genre -> (holder as GenreViewHolder).bind(item, listener)
|
||||||
when (item) {
|
is Header -> (holder as HeaderViewHolder).bind(item)
|
||||||
is Song -> (viewHolder as SongViewHolder).bind(item, listener)
|
|
||||||
is Album -> (viewHolder as AlbumViewHolder).bind(item, listener)
|
|
||||||
is Artist -> (viewHolder as ArtistViewHolder).bind(item, listener)
|
|
||||||
is Genre -> (viewHolder as GenreViewHolder).bind(item, listener)
|
|
||||||
is Header -> (viewHolder as HeaderViewHolder).bind(item, Unit)
|
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun submitList(list: List<Item>, callback: () -> Unit) = differ.submitList(list, callback)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFFER =
|
private val DIFFER =
|
||||||
object : SimpleItemCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
|
|
|
@ -106,7 +106,7 @@ class SearchFragment :
|
||||||
|
|
||||||
binding.searchRecycler.apply {
|
binding.searchRecycler.apply {
|
||||||
adapter = searchAdapter
|
adapter = searchAdapter
|
||||||
setSpanSizeLookup { pos -> searchAdapter.data.getItem(pos) is Header }
|
setSpanSizeLookup { pos -> searchModel.searchResults.value[pos] is Header }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
@ -154,7 +154,7 @@ class SearchFragment :
|
||||||
private fun updateResults(results: List<Item>) {
|
private fun updateResults(results: List<Item>) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
||||||
searchAdapter.data.submitList(results.toMutableList()) {
|
searchAdapter.submitList(results.toMutableList()) {
|
||||||
// I would make it so that the position is only scrolled back to the top when
|
// I would make it so that the position is only scrolled back to the top when
|
||||||
// the query actually changes instead of once every re-creation event, but sadly
|
// the query actually changes instead of once every re-creation event, but sadly
|
||||||
// that doesn't seem possible.
|
// that doesn't seem possible.
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui.accent
|
package org.oxycblt.auxio.ui.accent
|
||||||
|
|
||||||
import android.content.Context
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.ItemAccentBinding
|
import org.oxycblt.auxio.databinding.ItemAccentBinding
|
||||||
import org.oxycblt.auxio.ui.recycler.BackingData
|
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getColorCompat
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
@ -32,25 +31,29 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* An adapter that displays the accent palette.
|
* An adapter that displays the accent palette.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class AccentAdapter(listener: Listener) :
|
class AccentAdapter(private val listener: Listener) : RecyclerView.Adapter<AccentViewHolder>() {
|
||||||
MonoAdapter<Accent, AccentAdapter.Listener, AccentViewHolder>(listener) {
|
|
||||||
var selectedAccent: Accent? = null
|
var selectedAccent: Accent? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override val data = AccentData()
|
override fun getItemCount() = Accent.MAX
|
||||||
override val creator = AccentViewHolder.CREATOR
|
|
||||||
|
|
||||||
override fun onBind(
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = AccentViewHolder.new(parent)
|
||||||
viewHolder: AccentViewHolder,
|
|
||||||
item: Accent,
|
override fun onBindViewHolder(holder: AccentViewHolder, position: Int) =
|
||||||
listener: Listener,
|
throw IllegalStateException()
|
||||||
payload: List<Any>
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: AccentViewHolder,
|
||||||
|
position: Int,
|
||||||
|
payloads: MutableList<Any>
|
||||||
) {
|
) {
|
||||||
if (payload.isEmpty()) {
|
val item = Accent.from(position)
|
||||||
super.onBind(viewHolder, item, listener, payload)
|
|
||||||
|
if (payloads.isEmpty()) {
|
||||||
|
holder.bind(item, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewHolder.setSelected(item == selectedAccent)
|
holder.setSelected(item == selectedAccent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedAccent(accent: Accent) {
|
fun setSelectedAccent(accent: Accent) {
|
||||||
|
@ -64,20 +67,15 @@ class AccentAdapter(listener: Listener) :
|
||||||
fun onAccentSelected(accent: Accent)
|
fun onAccentSelected(accent: Accent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccentData : BackingData<Accent>() {
|
|
||||||
override fun getItem(position: Int) = Accent.from(position)
|
|
||||||
override fun getItemCount() = Accent.MAX
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val PAYLOAD_SELECTION_CHANGED = Any()
|
val PAYLOAD_SELECTION_CHANGED = Any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccentViewHolder private constructor(private val binding: ItemAccentBinding) :
|
class AccentViewHolder private constructor(private val binding: ItemAccentBinding) :
|
||||||
BindingViewHolder<Accent, AccentAdapter.Listener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Accent, listener: AccentAdapter.Listener) {
|
fun bind(item: Accent, listener: AccentAdapter.Listener) {
|
||||||
setSelected(false)
|
setSelected(false)
|
||||||
|
|
||||||
binding.accent.apply {
|
binding.accent.apply {
|
||||||
|
@ -101,13 +99,6 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
fun new(parent: View) = AccentViewHolder(ItemAccentBinding.inflate(parent.context.inflater))
|
||||||
object : Creator<AccentViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
|
||||||
AccentViewHolder(ItemAccentBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,10 @@ import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getDimenSize
|
import org.oxycblt.auxio.util.getDimenSize
|
||||||
import org.oxycblt.auxio.util.isRtl
|
import org.oxycblt.auxio.util.isRtl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal view responsible for the fast scroller popup.
|
||||||
|
* @author OxygenCobalt, Hai Zhang
|
||||||
|
*/
|
||||||
class FastScrollPopupView
|
class FastScrollPopupView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
|
||||||
|
|
|
@ -17,145 +17,13 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui.recycler
|
package org.oxycblt.auxio.ui.recycler
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
// TODO: Unify music updates and sorts under replace
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An adapter for one viewholder tied to one type of data. All functionality is derived from the
|
|
||||||
* overridden values.
|
|
||||||
* @author OxygenCobalt
|
|
||||||
*/
|
|
||||||
abstract class MonoAdapter<T, L, VH : BindingViewHolder<T, L>>(private val listener: L) :
|
|
||||||
RecyclerView.Adapter<VH>() {
|
|
||||||
/** The data that the adapter will source to bind viewholders. */
|
|
||||||
abstract val data: BackingData<T>
|
|
||||||
/** The creator instance that all viewholders will be derived from. */
|
|
||||||
protected abstract val creator: BindingViewHolder.Creator<VH>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional override to further modify the given [viewHolder]. The normal operation is to
|
|
||||||
* bind the viewholder.
|
|
||||||
*/
|
|
||||||
open fun onBind(viewHolder: VH, item: T, listener: L, payload: List<Any>) {
|
|
||||||
viewHolder.bind(item, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = data.getItemCount()
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
|
||||||
creator.create(parent.context)
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: VH, position: Int, payload: List<Any>) {
|
|
||||||
onBind(viewHolder, data.getItem(position), listener, payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private typealias AnyCreator = BindingViewHolder.Creator<out RecyclerView.ViewHolder>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An adapter for many viewholders tied to many types of data. Deriving this is more complicated
|
|
||||||
* than [MonoAdapter], as less overrides can be provided "for free".
|
|
||||||
* @author OxygenCobalt
|
|
||||||
*/
|
|
||||||
abstract class MultiAdapter<L>(private val listener: L) :
|
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
|
|
||||||
/** The data that the adapter will source to bind viewholders. */
|
|
||||||
abstract val data: BackingData<Item>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get any creator from the given item. This is used to derive the view type. If there is no
|
|
||||||
* creator for the given item, return null.
|
|
||||||
*/
|
|
||||||
protected abstract fun getCreatorFromItem(item: Item): AnyCreator?
|
|
||||||
/**
|
|
||||||
* Get any creator from the given view type. This is used to create the viewholder itself.
|
|
||||||
* Ideally, one should compare the viewType to every creator's view type and return the one that
|
|
||||||
* matches. In cases where the view type is unexpected, return null.
|
|
||||||
*/
|
|
||||||
protected abstract fun getCreatorFromViewType(viewType: Int): AnyCreator?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind the given viewholder to an item. Casting must be done on the consumer's end due to
|
|
||||||
* bounds on [BindingViewHolder].
|
|
||||||
*/
|
|
||||||
protected abstract fun onBind(
|
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
|
||||||
item: Item,
|
|
||||||
listener: L,
|
|
||||||
payload: List<Any>
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = data.getItemCount()
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) =
|
|
||||||
requireNotNull(getCreatorFromItem(data.getItem(position))) {
|
|
||||||
"Unable to get view type for item ${data.getItem(position)}"
|
|
||||||
}
|
|
||||||
.viewType
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
|
||||||
requireNotNull(getCreatorFromViewType(viewType)) {
|
|
||||||
"Unable to create viewholder for view type $viewType"
|
|
||||||
}
|
|
||||||
.create(parent.context)
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
|
||||||
position: Int,
|
|
||||||
payload: List<Any>
|
|
||||||
) {
|
|
||||||
onBind(viewHolder, data.getItem(position), listener, payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variation of [RecyclerView.ViewHolder] that enables ViewBinding. This is be used to provide a
|
|
||||||
* universal surface for binding data to a ViewHolder, and can be used with [MonoAdapter] to get an
|
|
||||||
* entire adapter implementation for free.
|
|
||||||
* @author OxygenCobalt
|
|
||||||
*/
|
|
||||||
abstract class BindingViewHolder<T, L>(root: View) : RecyclerView.ViewHolder(root) {
|
|
||||||
abstract fun bind(item: T, listener: L)
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Force the layout to *actually* be the screen width
|
|
||||||
root.layoutParams =
|
|
||||||
RecyclerView.LayoutParams(
|
|
||||||
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Creator<VH : RecyclerView.ViewHolder> {
|
|
||||||
val viewType: Int
|
|
||||||
fun create(context: Context): VH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An interface for detecting if an item has been clicked once. */
|
|
||||||
interface ItemClickListener {
|
|
||||||
/** Called when an item is clicked once. */
|
|
||||||
fun onItemClick(item: Item)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An interface for detecting if an item has had it's menu opened. */
|
|
||||||
interface MenuItemListener : ItemClickListener {
|
|
||||||
/** Called when an item desires to open a menu relating to it. */
|
|
||||||
fun onOpenMenu(item: Item, anchor: View)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base for all items in Auxio. Any datatype can derive this type and gain some behavior not
|
* The base for all items in Auxio. Any datatype can derive this type and gain some behavior not
|
||||||
* provided for free by the normal adapter implementations, such as certain types of diffing.
|
* provided for free by the normal adapter implementations, such as certain types of diffing.
|
||||||
|
@ -174,85 +42,49 @@ data class Header(
|
||||||
get() = string.toLong()
|
get() = string.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** An interface for detecting if an item has been clicked once. */
|
||||||
* Represents data that backs a [MonoAdapter] or [MultiAdapter]. This can be implemented by any
|
interface ItemClickListener {
|
||||||
* datatype to customize the organization or editing of data in a way that works best for the
|
/** Called when an item is clicked once. */
|
||||||
* specific adapter.
|
fun onItemClick(item: Item)
|
||||||
*/
|
|
||||||
abstract class BackingData<T> {
|
|
||||||
/** Get an item at [position]. */
|
|
||||||
abstract fun getItem(position: Int): T
|
|
||||||
/** Get the total length of the backing data. */
|
|
||||||
abstract fun getItemCount(): Int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** An interface for detecting if an item has had it's menu opened. */
|
||||||
* A list-backed [BackingData] that is modified synchronously. This is generally the recommended
|
interface MenuItemListener : ItemClickListener {
|
||||||
* option for most adapters.
|
/** Called when an item desires to open a menu relating to it. */
|
||||||
* @author OxygenCobalt
|
fun onOpenMenu(item: Item, anchor: View)
|
||||||
*/
|
|
||||||
class SyncBackingData<T>(adapter: RecyclerView.Adapter<*>, diffCallback: DiffUtil.ItemCallback<T>) :
|
|
||||||
BackingData<T>() {
|
|
||||||
private var differ = SyncListDiffer(adapter, diffCallback)
|
|
||||||
/** The current list backing this adapter. */
|
|
||||||
val currentList: List<T>
|
|
||||||
get() = differ.currentList
|
|
||||||
|
|
||||||
override fun getItem(position: Int): T = differ.currentList[position]
|
|
||||||
override fun getItemCount(): Int = differ.currentList.size
|
|
||||||
|
|
||||||
/** Submit a list normally, doing a diff synchronously. Only use this for trivial changes. */
|
|
||||||
fun submitList(newList: List<T>) {
|
|
||||||
differ.currentList = newList
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace this list with a new list. This is useful for very large list diffs that would
|
|
||||||
* generally be too chaotic and slow to provide a good UX.
|
|
||||||
*/
|
|
||||||
fun replaceList(newList: List<T>) {
|
|
||||||
if (newList == differ.currentList) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
differ.currentList = emptyList()
|
|
||||||
differ.currentList = newList
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like [AsyncListDiffer], but synchronous. This may seem like it would be inefficient, but in
|
* Like [AsyncListDiffer], but synchronous. This may seem like it would be inefficient, but in
|
||||||
* practice Auxio's lists tend to be small enough to the point where this does not matter, and
|
* practice Auxio's lists tend to be small enough to the point where this does not matter, and
|
||||||
* situations that would be inefficient are ruled out with [SyncBackingData.replaceList].
|
* situations that would be inefficient are ruled out with [replaceList].
|
||||||
*/
|
*/
|
||||||
private class SyncListDiffer<T>(
|
class SyncListDiffer<T>(
|
||||||
adapter: RecyclerView.Adapter<*>,
|
adapter: RecyclerView.Adapter<*>,
|
||||||
private val diffCallback: DiffUtil.ItemCallback<T>
|
private val diffCallback: DiffUtil.ItemCallback<T>
|
||||||
) {
|
) {
|
||||||
private val updateCallback = AdapterListUpdateCallback(adapter)
|
private val updateCallback = AdapterListUpdateCallback(adapter)
|
||||||
|
|
||||||
private var _currentList: List<T> = emptyList()
|
var currentList: List<T> = emptyList()
|
||||||
var currentList: List<T>
|
private set(newList) {
|
||||||
get() = _currentList
|
if (newList === currentList || newList.isEmpty() && currentList.isEmpty()) {
|
||||||
set(newList) {
|
|
||||||
if (newList === _currentList || newList.isEmpty() && _currentList.isEmpty()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newList.isEmpty()) {
|
if (newList.isEmpty()) {
|
||||||
val oldListSize = _currentList.size
|
val oldListSize = currentList.size
|
||||||
_currentList = emptyList()
|
field = emptyList()
|
||||||
updateCallback.onRemoved(0, oldListSize)
|
updateCallback.onRemoved(0, oldListSize)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentList.isEmpty()) {
|
if (currentList.isEmpty()) {
|
||||||
_currentList = newList
|
field = newList
|
||||||
updateCallback.onInserted(0, newList.size)
|
updateCallback.onInserted(0, newList.size)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldList = _currentList
|
val oldList = currentList
|
||||||
val result =
|
val result =
|
||||||
DiffUtil.calculateDiff(
|
DiffUtil.calculateDiff(
|
||||||
object : DiffUtil.Callback() {
|
object : DiffUtil.Callback() {
|
||||||
|
@ -306,35 +138,26 @@ private class SyncListDiffer<T>(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
_currentList = newList
|
field = newList
|
||||||
result.dispatchUpdatesTo(updateCallback)
|
result.dispatchUpdatesTo(updateCallback)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** Submit a list normally, doing a diff synchronously. Only use this for trivial changes. */
|
||||||
* A list-backed [BackingData] that is modified with [AsyncListDiffer]. This is useful in cases
|
fun submitList(newList: List<T>) {
|
||||||
* where data updates are rapid-fire and unpredictable, and where the benefits of asynchronously
|
currentList = newList
|
||||||
* diffing the adapter outweigh the shortcomings.
|
}
|
||||||
* @author OxygenCobalt
|
|
||||||
*/
|
|
||||||
class AsyncBackingData<T>(
|
|
||||||
adapter: RecyclerView.Adapter<*>,
|
|
||||||
diffCallback: DiffUtil.ItemCallback<T>
|
|
||||||
) : BackingData<T>() {
|
|
||||||
private var differ = AsyncListDiffer(adapter, diffCallback)
|
|
||||||
/** The current list backing this adapter. */
|
|
||||||
val currentList: List<T>
|
|
||||||
get() = differ.currentList
|
|
||||||
|
|
||||||
override fun getItem(position: Int): T = differ.currentList[position]
|
|
||||||
override fun getItemCount(): Int = differ.currentList.size
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit a list for [AsyncListDiffer] to calculate. Any previous calls of [submitList] will be
|
* Replace this list with a new list. This is useful for very large list diffs that would
|
||||||
* dropped.
|
* generally be too chaotic and slow to provide a good UX.
|
||||||
*/
|
*/
|
||||||
fun submitList(newList: List<T>, onDone: () -> Unit = {}) {
|
fun replaceList(newList: List<T>) {
|
||||||
differ.submitList(newList, onDone)
|
if (newList == currentList) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentList = emptyList()
|
||||||
|
currentList = newList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui.recycler
|
package org.oxycblt.auxio.ui.recycler
|
||||||
|
|
||||||
import android.content.Context
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
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.ItemHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemHeaderBinding
|
||||||
|
@ -36,8 +37,8 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
BindingViewHolder<Song, MenuItemListener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(item: Song, listener: MenuItemListener) {
|
fun bind(item: Song, listener: MenuItemListener) {
|
||||||
binding.songAlbumCover.bind(item)
|
binding.songAlbumCover.bind(item)
|
||||||
binding.songName.text = item.resolveName(binding.context)
|
binding.songName.text = item.resolveName(binding.context)
|
||||||
binding.songInfo.text = item.resolveIndividualArtistName(binding.context)
|
binding.songInfo.text = item.resolveIndividualArtistName(binding.context)
|
||||||
|
@ -50,14 +51,9 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_SONG
|
||||||
object : Creator<SongViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_SONG
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) = SongViewHolder(ItemSongBinding.inflate(parent.context.inflater))
|
||||||
SongViewHolder(ItemSongBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Song>() {
|
object : SimpleItemCallback<Song>() {
|
||||||
|
@ -75,9 +71,9 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
class AlbumViewHolder
|
class AlbumViewHolder
|
||||||
private constructor(
|
private constructor(
|
||||||
private val binding: ItemParentBinding,
|
private val binding: ItemParentBinding,
|
||||||
) : BindingViewHolder<Album, MenuItemListener>(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Album, listener: MenuItemListener) {
|
fun bind(item: Album, listener: MenuItemListener) {
|
||||||
binding.parentImage.bind(item)
|
binding.parentImage.bind(item)
|
||||||
binding.parentName.text = item.resolveName(binding.context)
|
binding.parentName.text = item.resolveName(binding.context)
|
||||||
binding.parentInfo.text = item.artist.resolveName(binding.context)
|
binding.parentInfo.text = item.artist.resolveName(binding.context)
|
||||||
|
@ -90,14 +86,9 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ALBUM
|
||||||
object : Creator<AlbumViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ALBUM
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) = AlbumViewHolder(ItemParentBinding.inflate(parent.context.inflater))
|
||||||
AlbumViewHolder(ItemParentBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Album>() {
|
object : SimpleItemCallback<Album>() {
|
||||||
|
@ -114,9 +105,9 @@ private constructor(
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class ArtistViewHolder private constructor(private val binding: ItemParentBinding) :
|
class ArtistViewHolder private constructor(private val binding: ItemParentBinding) :
|
||||||
BindingViewHolder<Artist, MenuItemListener>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Artist, listener: MenuItemListener) {
|
fun bind(item: Artist, listener: MenuItemListener) {
|
||||||
binding.parentImage.bind(item)
|
binding.parentImage.bind(item)
|
||||||
binding.parentName.text = item.resolveName(binding.context)
|
binding.parentName.text = item.resolveName(binding.context)
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
|
@ -133,14 +124,9 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ARTIST
|
||||||
object : Creator<ArtistViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_ARTIST
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) = ArtistViewHolder(ItemParentBinding.inflate(parent.context.inflater))
|
||||||
ArtistViewHolder(ItemParentBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Artist>() {
|
object : SimpleItemCallback<Artist>() {
|
||||||
|
@ -159,9 +145,9 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
||||||
class GenreViewHolder
|
class GenreViewHolder
|
||||||
private constructor(
|
private constructor(
|
||||||
private val binding: ItemParentBinding,
|
private val binding: ItemParentBinding,
|
||||||
) : BindingViewHolder<Genre, MenuItemListener>(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Genre, listener: MenuItemListener) {
|
fun bind(item: Genre, listener: MenuItemListener) {
|
||||||
binding.parentImage.bind(item)
|
binding.parentImage.bind(item)
|
||||||
binding.parentName.text = item.resolveName(binding.context)
|
binding.parentName.text = item.resolveName(binding.context)
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
|
@ -175,14 +161,9 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_GENRE
|
||||||
object : Creator<GenreViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_GENRE
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) = GenreViewHolder(ItemParentBinding.inflate(parent.context.inflater))
|
||||||
GenreViewHolder(ItemParentBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Genre>() {
|
object : SimpleItemCallback<Genre>() {
|
||||||
|
@ -197,21 +178,16 @@ private constructor(
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class HeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
|
class HeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
|
||||||
BindingViewHolder<Header, Unit>(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(item: Header, listener: Unit) {
|
fun bind(item: Header) {
|
||||||
binding.title.text = binding.context.getString(item.string)
|
binding.title.text = binding.context.getString(item.string)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CREATOR =
|
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_HEADER
|
||||||
object : Creator<HeaderViewHolder> {
|
|
||||||
override val viewType: Int
|
|
||||||
get() = IntegerTable.ITEM_TYPE_HEADER
|
|
||||||
|
|
||||||
override fun create(context: Context) =
|
fun new(parent: View) = HeaderViewHolder(ItemHeaderBinding.inflate(parent.context.inflater))
|
||||||
HeaderViewHolder(ItemHeaderBinding.inflate(context.inflater))
|
|
||||||
}
|
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : SimpleItemCallback<Header>() {
|
object : SimpleItemCallback<Header>() {
|
||||||
|
|
|
@ -78,10 +78,9 @@ own function, with the binding being obtained by calling `requireBinding`.
|
||||||
At times it may be more appropriate to use a `View` instead of a fragment. This is okay as long as
|
At times it may be more appropriate to use a `View` instead of a fragment. This is okay as long as
|
||||||
view-binding is still used.
|
view-binding is still used.
|
||||||
|
|
||||||
Auxio uses `RecyclerView` for all list information. Due to the complexities of Auxio, the way one
|
Auxio uses `RecyclerView` for all list information. To manage some complexity, there are a few
|
||||||
defines an adapter differs quite heavily from the normal library. Generally, start with
|
conventions that are used when creating adapters. These can be seen in the `RecyclerFramework`
|
||||||
`MonoAdapter` for a list with one type of data and `MultiAdapter` for lists with many types of data,
|
file and in adapter implementations.
|
||||||
then follow the documentation to see how to fully implement the class.
|
|
||||||
|
|
||||||
#### Object communication
|
#### Object communication
|
||||||
Auxio's codebase is mostly centered around 4 different types of code that communicates with
|
Auxio's codebase is mostly centered around 4 different types of code that communicates with
|
||||||
|
|
Loading…
Reference in a new issue