list: make listeners generic

Make all list listeners operate on generic values.

Wanted to do this for awhile, but couldn't figure out how to get the
listener to work with sub-typed listeners until I learned what the in
keyword actually does. This removes a ton of type-checking boilerplate
that it's not even funny.
This commit is contained in:
Alexander Capehart 2023-01-04 09:40:25 -07:00
parent d5941aa705
commit d55dfbc849
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
34 changed files with 168 additions and 203 deletions

View file

@ -29,7 +29,6 @@ import com.google.android.material.transition.MaterialSharedAxis
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
@ -39,18 +38,14 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.*
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A [ListFragment] that shows information about an [Album]. * A [ListFragment] that shows information about an [Album].
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAdapter.Listener { class AlbumDetailFragment :
ListFragment<Music, FragmentDetailBinding>(), AlbumDetailAdapter.Listener {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
// Information about what album to display is initially within the navigation arguments // Information about what album to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an album. // as a UID, as that is the only safe way to parcel an album.
@ -126,20 +121,20 @@ class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAd
} }
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Music) {
check(music is Song) { "Unexpected datatype: ${music::class.java}" } val song = requireIs<Song>(item)
when (Settings(requireContext()).detailPlaybackMode) { when (Settings(requireContext()).detailPlaybackMode) {
// "Play from shown item" and "Play from album" functionally have the same // "Play from shown item" and "Play from album" functionally have the same
// behavior since a song can only have one album. // behavior since a song can only have one album.
null, null,
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(song)
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(song)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(song)
MusicMode.GENRES -> playbackModel.playFromGenre(music) MusicMode.GENRES -> playbackModel.playFromGenre(song)
} }
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" } check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" }
openMusicMenu(anchor, R.menu.menu_album_song_actions, item) openMusicMenu(anchor, R.menu.menu_album_song_actions, item)
} }

View file

@ -29,7 +29,6 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter
import org.oxycblt.auxio.detail.recycler.DetailAdapter import org.oxycblt.auxio.detail.recycler.DetailAdapter
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
@ -49,7 +48,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* A [ListFragment] that shows information about an [Artist]. * A [ListFragment] that shows information about an [Artist].
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapter.Listener { class ArtistDetailFragment : ListFragment<Music, FragmentDetailBinding>(), DetailAdapter.Listener {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
// Information about what artist to display is initially within the navigation arguments // Information about what artist to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an artist. // as a UID, as that is the only safe way to parcel an artist.
@ -121,27 +120,27 @@ class ArtistDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapte
} }
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Music) {
when (music) { when (item) {
is Song -> { is Song -> {
when (Settings(requireContext()).detailPlaybackMode) { when (Settings(requireContext()).detailPlaybackMode) {
// When configured to play from the selected item, we already have an Artist // When configured to play from the selected item, we already have an Artist
// to play from. // to play from.
null -> null ->
playbackModel.playFromArtist( playbackModel.playFromArtist(
music, unlikelyToBeNull(detailModel.currentArtist.value)) item, unlikelyToBeNull(detailModel.currentArtist.value))
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> playbackModel.playFromGenre(music) MusicMode.GENRES -> playbackModel.playFromGenre(item)
} }
} }
is Album -> navModel.exploreNavigateTo(music) is Album -> navModel.exploreNavigateTo(item)
else -> error("Unexpected datatype: ${music::class.simpleName}") else -> error("Unexpected datatype: ${item::class.simpleName}")
} }
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Song -> openMusicMenu(anchor, R.menu.menu_artist_song_actions, item) is Song -> openMusicMenu(anchor, R.menu.menu_artist_song_actions, item)
is Album -> openMusicMenu(anchor, R.menu.menu_artist_album_actions, item) is Album -> openMusicMenu(anchor, R.menu.menu_artist_album_actions, item)

View file

@ -29,7 +29,6 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.DetailAdapter import org.oxycblt.auxio.detail.recycler.DetailAdapter
import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
@ -50,7 +49,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* A [ListFragment] that shows information for a particular [Genre]. * A [ListFragment] that shows information for a particular [Genre].
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenreDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapter.Listener { class GenreDetailFragment : ListFragment<Music, FragmentDetailBinding>(), DetailAdapter.Listener {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
// Information about what genre to display is initially within the navigation arguments // Information about what genre to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an genre. // as a UID, as that is the only safe way to parcel an genre.
@ -120,26 +119,26 @@ class GenreDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapter
} }
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Music) {
when (music) { when (item) {
is Artist -> navModel.exploreNavigateTo(music) is Artist -> navModel.exploreNavigateTo(item)
is Song -> is Song ->
when (Settings(requireContext()).detailPlaybackMode) { when (Settings(requireContext()).detailPlaybackMode) {
// When configured to play from the selected item, we already have a Genre // When configured to play from the selected item, we already have a Genre
// to play from. // to play from.
null -> null ->
playbackModel.playFromGenre( playbackModel.playFromGenre(
music, unlikelyToBeNull(detailModel.currentGenre.value)) item, unlikelyToBeNull(detailModel.currentGenre.value))
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> playbackModel.playFromGenre(music) MusicMode.GENRES -> playbackModel.playFromGenre(item)
} }
else -> error("Unexpected datatype: ${music::class.simpleName}") else -> error("Unexpected datatype: ${item::class.simpleName}")
} }
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Artist -> openMusicMenu(anchor, R.menu.menu_artist_actions, item) is Artist -> openMusicMenu(anchor, R.menu.menu_artist_actions, item)
is Song -> openMusicMenu(anchor, R.menu.menu_song_actions, item) is Song -> openMusicMenu(anchor, R.menu.menu_song_actions, item)
@ -184,17 +183,15 @@ class GenreDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapter
} }
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
var item: Item? = null var playingMusic: Music? = null
if (parent is Artist) { if (parent is Artist) {
item = parent playingMusic = parent
} }
// Prefer songs that might be playing from this genre.
if (parent is Genre && parent.uid == unlikelyToBeNull(detailModel.currentGenre.value).uid) { if (parent is Genre && parent.uid == unlikelyToBeNull(detailModel.currentGenre.value).uid) {
item = song playingMusic = song
} }
detailAdapter.setPlayingItem(playingMusic, isPlaying)
detailAdapter.setPlayingItem(item, isPlaying)
} }
private fun handleNavigation(item: Music?) { private fun handleNavigation(item: Music?) {

View file

@ -226,7 +226,7 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
* @param song The new [Song] to bind. * @param song The new [Song] to bind.
* @param listener A [SelectableListListener] to bind interactions to. * @param listener A [SelectableListListener] to bind interactions to.
*/ */
fun bind(song: Song, listener: SelectableListListener) { fun bind(song: Song, listener: SelectableListListener<Song>) {
listener.bind(song, this, menuButton = binding.songMenu) listener.bind(song, this, menuButton = binding.songMenu)
binding.songTrack.apply { binding.songTrack.apply {

View file

@ -183,7 +183,7 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
* @param album The new [Album] to bind. * @param album The new [Album] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
fun bind(album: Album, listener: SelectableListListener) { fun bind(album: Album, listener: SelectableListListener<Album>) {
listener.bind(album, this, menuButton = binding.parentMenu) listener.bind(album, this, menuButton = binding.parentMenu)
binding.parentImage.bind(album) binding.parentImage.bind(album)
binding.parentName.text = album.resolveName(binding.context) binding.parentName.text = album.resolveName(binding.context)
@ -235,7 +235,7 @@ private class ArtistSongViewHolder private constructor(private val binding: Item
* @param song The new [Song] to bind. * @param song The new [Song] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
fun bind(song: Song, listener: SelectableListListener) { fun bind(song: Song, listener: SelectableListListener<Song>) {
listener.bind(song, this, menuButton = binding.songMenu) listener.bind(song, this, menuButton = binding.songMenu)
binding.songAlbumCover.bind(song) binding.songAlbumCover.bind(song)
binding.songName.text = song.resolveName(binding.context) binding.songName.text = song.resolveName(binding.context)

View file

@ -30,6 +30,7 @@ import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.recycler.* import org.oxycblt.auxio.list.recycler.*
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
@ -88,7 +89,7 @@ abstract class DetailAdapter(
} }
/** An extended [SelectableListListener] for [DetailAdapter] implementations. */ /** An extended [SelectableListListener] for [DetailAdapter] implementations. */
interface Listener : SelectableListListener { interface Listener : SelectableListListener<Music> {
// TODO: Split off into sub-listeners if a collapsing toolbar is implemented. // TODO: Split off into sub-listeners if a collapsing toolbar is implemented.
/** /**
* Called when the play button in a detail header is pressed, requesting that the current * Called when the play button in a detail header is pressed, requesting that the current

View file

@ -33,11 +33,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.list.recycler.AlbumViewHolder
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.list.recycler.SyncListDiffer
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.playback.secsToMs
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -47,7 +43,7 @@ import org.oxycblt.auxio.util.collectImmediately
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumListFragment : class AlbumListFragment :
ListFragment<FragmentHomeListBinding>(), ListFragment<Album, FragmentHomeListBinding>(),
FastScrollRecyclerView.Listener, FastScrollRecyclerView.Listener,
FastScrollRecyclerView.PopupProvider { FastScrollRecyclerView.PopupProvider {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
@ -125,13 +121,11 @@ class AlbumListFragment :
homeModel.setFastScrolling(isFastScrolling) homeModel.setFastScrolling(isFastScrolling)
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Album) {
check(music is Album) { "Unexpected datatype: ${music::class.java}" } navModel.exploreNavigateTo(item)
navModel.exploreNavigateTo(music)
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Album, anchor: View) {
check(item is Album) { "Unexpected datatype: ${item::class.java}" }
openMusicMenu(anchor, R.menu.menu_album_actions, item) openMusicMenu(anchor, R.menu.menu_album_actions, item)
} }
@ -144,7 +138,7 @@ class AlbumListFragment :
* A [SelectionIndicatorAdapter] that shows a list of [Album]s using [AlbumViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Album]s using [AlbumViewHolder].
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class AlbumAdapter(private val listener: SelectableListListener) : private class AlbumAdapter(private val listener: SelectableListListener<Album>) :
SelectionIndicatorAdapter<AlbumViewHolder>() { SelectionIndicatorAdapter<AlbumViewHolder>() {
private val differ = SyncListDiffer(this, AlbumViewHolder.DIFF_CALLBACK) private val differ = SyncListDiffer(this, AlbumViewHolder.DIFF_CALLBACK)

View file

@ -32,7 +32,6 @@ import org.oxycblt.auxio.list.recycler.ArtistViewHolder
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.list.recycler.SyncListDiffer
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.Sort
@ -45,7 +44,7 @@ import org.oxycblt.auxio.util.nonZeroOrNull
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistListFragment : class ArtistListFragment :
ListFragment<FragmentHomeListBinding>(), ListFragment<Artist, FragmentHomeListBinding>(),
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
@ -100,13 +99,11 @@ class ArtistListFragment :
homeModel.setFastScrolling(isFastScrolling) homeModel.setFastScrolling(isFastScrolling)
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Artist) {
check(music is Artist) { "Unexpected datatype: ${music::class.java}" } navModel.exploreNavigateTo(item)
navModel.exploreNavigateTo(music)
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Artist, anchor: View) {
check(item is Artist) { "Unexpected datatype: ${item::class.java}" }
openMusicMenu(anchor, R.menu.menu_artist_actions, item) openMusicMenu(anchor, R.menu.menu_artist_actions, item)
} }
@ -119,7 +116,7 @@ class ArtistListFragment :
* A [SelectionIndicatorAdapter] that shows a list of [Artist]s using [ArtistViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Artist]s using [ArtistViewHolder].
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class ArtistAdapter(private val listener: SelectableListListener) : private class ArtistAdapter(private val listener: SelectableListListener<Artist>) :
SelectionIndicatorAdapter<ArtistViewHolder>() { SelectionIndicatorAdapter<ArtistViewHolder>() {
private val differ = SyncListDiffer(this, ArtistViewHolder.DIFF_CALLBACK) private val differ = SyncListDiffer(this, ArtistViewHolder.DIFF_CALLBACK)

View file

@ -32,7 +32,6 @@ import org.oxycblt.auxio.list.recycler.GenreViewHolder
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.list.recycler.SyncListDiffer
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.Sort
@ -44,7 +43,7 @@ import org.oxycblt.auxio.util.collectImmediately
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenreListFragment : class GenreListFragment :
ListFragment<FragmentHomeListBinding>(), ListFragment<Genre, FragmentHomeListBinding>(),
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
@ -99,13 +98,11 @@ class GenreListFragment :
homeModel.setFastScrolling(isFastScrolling) homeModel.setFastScrolling(isFastScrolling)
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Genre) {
check(music is Genre) { "Unexpected datatype: ${music::class.java}" } navModel.exploreNavigateTo(item)
navModel.exploreNavigateTo(music)
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Genre, anchor: View) {
check(item is Genre) { "Unexpected datatype: ${item::class.java}" }
openMusicMenu(anchor, R.menu.menu_artist_actions, item) openMusicMenu(anchor, R.menu.menu_artist_actions, item)
} }
@ -118,7 +115,7 @@ class GenreListFragment :
* A [SelectionIndicatorAdapter] that shows a list of [Genre]s using [GenreViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Genre]s using [GenreViewHolder].
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class GenreAdapter(private val listener: SelectableListListener) : private class GenreAdapter(private val listener: SelectableListListener<Genre>) :
SelectionIndicatorAdapter<GenreViewHolder>() { SelectionIndicatorAdapter<GenreViewHolder>() {
private val differ = SyncListDiffer(this, GenreViewHolder.DIFF_CALLBACK) private val differ = SyncListDiffer(this, GenreViewHolder.DIFF_CALLBACK)

View file

@ -33,7 +33,6 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.list.recycler.SyncListDiffer
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -48,7 +47,7 @@ import org.oxycblt.auxio.util.collectImmediately
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SongListFragment : class SongListFragment :
ListFragment<FragmentHomeListBinding>(), ListFragment<Song, FragmentHomeListBinding>(),
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
@ -130,18 +129,16 @@ class SongListFragment :
homeModel.setFastScrolling(isFastScrolling) homeModel.setFastScrolling(isFastScrolling)
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Song) {
check(music is Song) { "Unexpected datatype: ${music::class.java}" }
when (Settings(requireContext()).libPlaybackMode) { when (Settings(requireContext()).libPlaybackMode) {
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> playbackModel.playFromGenre(music) MusicMode.GENRES -> playbackModel.playFromGenre(item)
} }
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Song, anchor: View) {
check(item is Song) { "Unexpected datatype: ${item::class.java}" }
openMusicMenu(anchor, R.menu.menu_song_actions, item) openMusicMenu(anchor, R.menu.menu_song_actions, item)
} }
@ -158,7 +155,7 @@ class SongListFragment :
* A [SelectionIndicatorAdapter] that shows a list of [Song]s using [SongViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Song]s using [SongViewHolder].
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class SongAdapter(private val listener: SelectableListListener) : private class SongAdapter(private val listener: SelectableListListener<Song>) :
SelectionIndicatorAdapter<SongViewHolder>() { SelectionIndicatorAdapter<SongViewHolder>() {
private val differ = SyncListDiffer(this, SongViewHolder.DIFF_CALLBACK) private val differ = SyncListDiffer(this, SongViewHolder.DIFF_CALLBACK)

View file

@ -17,7 +17,6 @@
package org.oxycblt.auxio.home.tabs package org.oxycblt.auxio.home.tabs
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
@ -26,7 +25,7 @@ import org.oxycblt.auxio.util.logE
* @param mode The type of list in the home view this instance corresponds to. * @param mode The type of list in the home view this instance corresponds to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed class Tab(open val mode: MusicMode) : Item { sealed class Tab(open val mode: MusicMode) {
/** /**
* A visible tab. This will be visible in the home and tab configuration views. * A visible tab. This will be visible in the home and tab configuration views.
* @param mode The type of list in the home view this instance corresponds to. * @param mode The type of list in the home view this instance corresponds to.

View file

@ -32,7 +32,7 @@ import org.oxycblt.auxio.util.inflater
* A [RecyclerView.Adapter] that displays an array of [Tab]s open for configuration. * A [RecyclerView.Adapter] that displays an array of [Tab]s open for configuration.
* @param listener A [EditableListListener] for tab interactions. * @param listener A [EditableListListener] for tab interactions.
*/ */
class TabAdapter(private val listener: EditableListListener) : class TabAdapter(private val listener: EditableListListener<Tab>) :
RecyclerView.Adapter<TabViewHolder>() { RecyclerView.Adapter<TabViewHolder>() {
/** The current array of [Tab]s. */ /** The current array of [Tab]s. */
var tabs = arrayOf<Tab>() var tabs = arrayOf<Tab>()
@ -93,7 +93,7 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
* @param listener A [EditableListListener] to bind interactions to. * @param listener A [EditableListListener] to bind interactions to.
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
fun bind(tab: Tab, listener: EditableListListener) { fun bind(tab: Tab, listener: EditableListListener<Tab>) {
listener.bind(tab, this, dragHandle = binding.tabDragHandle) listener.bind(tab, this, dragHandle = binding.tabDragHandle)
binding.tabCheckBox.apply { binding.tabCheckBox.apply {
// Update the CheckBox name to align with the mode // Update the CheckBox name to align with the mode

View file

@ -26,7 +26,6 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogTabsBinding import org.oxycblt.auxio.databinding.DialogTabsBinding
import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.EditableListListener
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -35,7 +34,8 @@ import org.oxycblt.auxio.util.logD
* A [ViewBindingDialogFragment] that allows the user to modify the home [Tab] configuration. * A [ViewBindingDialogFragment] that allows the user to modify the home [Tab] configuration.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), EditableListListener { class TabCustomizeDialog :
ViewBindingDialogFragment<DialogTabsBinding>(), EditableListListener<Tab> {
private val tabAdapter = TabAdapter(this) private val tabAdapter = TabAdapter(this)
private var touchHelper: ItemTouchHelper? = null private var touchHelper: ItemTouchHelper? = null
@ -81,8 +81,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), Edita
binding.tabRecycler.adapter = null binding.tabRecycler.adapter = null
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Tab, viewHolder: RecyclerView.ViewHolder) {
check(item is Tab) { "Unexpected datatype: ${item::class.java}" }
// We will need the exact index of the tab to update on in order to // We will need the exact index of the tab to update on in order to
// notify the adapter of the change. // notify the adapter of the change.
val index = tabAdapter.tabs.indexOfFirst { it.mode == item.mode } val index = tabAdapter.tabs.indexOfFirst { it.mode == item.mode }

View file

@ -37,7 +37,8 @@ import org.oxycblt.auxio.util.showToast
* A Fragment containing a selectable list. * A Fragment containing a selectable list.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), SelectableListListener { abstract class ListFragment<in T : Music, VB : ViewBinding> :
SelectionFragment<VB>(), SelectableListListener<T> {
protected val navModel: NavigationViewModel by activityViewModels() protected val navModel: NavigationViewModel by activityViewModels()
private var currentMenu: PopupMenu? = null private var currentMenu: PopupMenu? = null
@ -50,12 +51,11 @@ abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), Selecta
/** /**
* Called when [onClick] is called, but does not result in the item being selected. This more or * Called when [onClick] is called, but does not result in the item being selected. This more or
* less corresponds to an [onClick] implementation in a non-[ListFragment]. * less corresponds to an [onClick] implementation in a non-[ListFragment].
* @param music The [Music] item that was clicked. * @param item The [T] data of the item that was clicked.
*/ */
abstract fun onRealClick(music: Music) abstract fun onRealClick(item: T)
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: T, viewHolder: RecyclerView.ViewHolder) {
check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" }
if (selectionModel.selected.value.isNotEmpty()) { if (selectionModel.selected.value.isNotEmpty()) {
// Map clicking an item to selecting an item when items are already selected. // Map clicking an item to selecting an item when items are already selected.
selectionModel.select(item) selectionModel.select(item)
@ -65,8 +65,7 @@ abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), Selecta
} }
} }
override fun onSelect(item: Item) { override fun onSelect(item: T) {
check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" }
selectionModel.select(item) selectionModel.select(item)
} }

View file

@ -25,26 +25,22 @@ import androidx.recyclerview.widget.RecyclerView
* A basic listener for list interactions. * A basic listener for list interactions.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface ClickableListListener { interface ClickableListListener<in T> {
/** /**
* Called when an [Item] in the list is clicked. * Called when an item in the list is clicked.
* @param item The [Item] that was clicked. * @param item The [T] item that was clicked.
* @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked. * @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked.
*/ */
fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) fun onClick(item: T, viewHolder: RecyclerView.ViewHolder)
/** /**
* Binds this instance to a list item. * Binds this instance to a list item.
* @param item The [Item] that this list entry is bound to. * @param item The [T] to bind this item to.
* @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked. * @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked.
* @param bodyView The [View] containing the main body of the list item. Any click events on * @param bodyView The [View] containing the main body of the list item. Any click events on
* this [View] are routed to the listener. Defaults to the root view. * this [View] are routed to the listener. Defaults to the root view.
*/ */
fun bind( fun bind(item: T, viewHolder: RecyclerView.ViewHolder, bodyView: View = viewHolder.itemView) {
item: Item,
viewHolder: RecyclerView.ViewHolder,
bodyView: View = viewHolder.itemView
) {
bodyView.setOnClickListener { onClick(item, viewHolder) } bodyView.setOnClickListener { onClick(item, viewHolder) }
} }
} }
@ -53,7 +49,7 @@ interface ClickableListListener {
* An extension of [ClickableListListener] that enables list editing functionality. * An extension of [ClickableListListener] that enables list editing functionality.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface EditableListListener : ClickableListListener { interface EditableListListener<in T> : ClickableListListener<T> {
/** /**
* Called when a [RecyclerView.ViewHolder] requests that it should be dragged. * Called when a [RecyclerView.ViewHolder] requests that it should be dragged.
* @param viewHolder The [RecyclerView.ViewHolder] that should start being dragged. * @param viewHolder The [RecyclerView.ViewHolder] that should start being dragged.
@ -62,14 +58,14 @@ interface EditableListListener : ClickableListListener {
/** /**
* Binds this instance to a list item. * Binds this instance to a list item.
* @param item The [Item] that this list entry is bound to. * @param item The [T] to bind this item to.
* @param viewHolder The [RecyclerView.ViewHolder] to bind. * @param viewHolder The [RecyclerView.ViewHolder] to bind.
* @param bodyView The [View] containing the main body of the list item. Any click events on * @param bodyView The [View] containing the main body of the list item. Any click events on
* this [View] are routed to the listener. Defaults to the root view. * this [View] are routed to the listener. Defaults to the root view.
* @param dragHandle A touchable [View]. Any drag on this view will start a drag event. * @param dragHandle A touchable [View]. Any drag on this view will start a drag event.
*/ */
fun bind( fun bind(
item: Item, item: T,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
bodyView: View = viewHolder.itemView, bodyView: View = viewHolder.itemView,
dragHandle: View dragHandle: View
@ -89,30 +85,30 @@ interface EditableListListener : ClickableListListener {
* An extension of [ClickableListListener] that enables menu and selection functionality. * An extension of [ClickableListListener] that enables menu and selection functionality.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface SelectableListListener : ClickableListListener { interface SelectableListListener<in T> : ClickableListListener<T> {
/** /**
* Called when an [Item] in the list requests that a menu related to it should be opened. * Called when an item in the list requests that a menu related to it should be opened.
* @param item The [Item] to show a menu for. * @param item The [T] item to open a menu for.
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
*/ */
fun onOpenMenu(item: Item, anchor: View) fun onOpenMenu(item: T, anchor: View)
/** /**
* Called when an [Item] in the list requests that it be selected. * Called when an item in the list requests that it be selected.
* @param item The [Item] to select. * @param item The [T] item to select.
*/ */
fun onSelect(item: Item) fun onSelect(item: T)
/** /**
* Binds this instance to a list item. * Binds this instance to a list item.
* @param item The [Item] that this list entry is bound to. * @param item The [T] to bind this item to.
* @param viewHolder The [RecyclerView.ViewHolder] to bind. * @param viewHolder The [RecyclerView.ViewHolder] to bind.
* @param bodyView The [View] containing the main body of the list item. Any click events on * @param bodyView The [View] containing the main body of the list item. Any click events on
* this [View] are routed to the listener. Defaults to the root view. * this [View] are routed to the listener. Defaults to the root view.
* @param menuButton A clickable [View]. Any click events on this [View] will open a menu. * @param menuButton A clickable [View]. Any click events on this [View] will open a menu.
*/ */
fun bind( fun bind(
item: Item, item: T,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
bodyView: View = viewHolder.itemView, bodyView: View = viewHolder.itemView,
menuButton: View menuButton: View

View file

@ -20,6 +20,7 @@ package org.oxycblt.auxio.list.recycler
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
/** /**
@ -31,7 +32,7 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
// - The currently playing item, which is usually marked as "selected" and becomes accented. // - The currently playing item, which is usually marked as "selected" and becomes accented.
// - Whether playback is ongoing, which corresponds to whether the item's ImageGroup is // - Whether playback is ongoing, which corresponds to whether the item's ImageGroup is
// marked as "playing" or not. // marked as "playing" or not.
private var currentItem: Item? = null private var currentMusic: Music? = null
private var isPlaying = false private var isPlaying = false
/** /**
@ -44,7 +45,7 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
override fun onBindViewHolder(holder: VH, position: Int, payloads: List<Any>) { override fun onBindViewHolder(holder: VH, position: Int, payloads: List<Any>) {
// Only try to update the playing indicator if the ViewHolder supports it // Only try to update the playing indicator if the ViewHolder supports it
if (holder is ViewHolder) { if (holder is ViewHolder) {
holder.updatePlayingIndicator(currentList[position] == currentItem, isPlaying) holder.updatePlayingIndicator(currentList[position] == currentMusic, isPlaying)
} }
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
@ -55,14 +56,14 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
} }
/** /**
* Update the currently playing item in the list. * Update the currently playing item in the list.
* @param item The item currently being played, or null if it is not being played. * @param music The [Music] currently being played, or null if it is not being played.
* @param isPlaying Whether playback is ongoing or paused. * @param isPlaying Whether playback is ongoing or paused.
*/ */
fun setPlayingItem(item: Item?, isPlaying: Boolean) { fun setPlayingItem(music: Music?, isPlaying: Boolean) {
var updatedItem = false var updatedItem = false
if (currentItem != item) { if (currentMusic != music) {
val oldItem = currentItem val oldItem = currentMusic
currentItem = item currentMusic = music
// Remove the playing indicator from the old item // Remove the playing indicator from the old item
if (oldItem != null) { if (oldItem != null) {
@ -75,8 +76,8 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
} }
// Enable the playing indicator on the new item // Enable the playing indicator on the new item
if (item != null) { if (music != null) {
val pos = currentList.indexOfFirst { it == item } val pos = currentList.indexOfFirst { it == music }
if (pos > -1) { if (pos > -1) {
notifyItemChanged(pos, PAYLOAD_PLAYING_INDICATOR_CHANGED) notifyItemChanged(pos, PAYLOAD_PLAYING_INDICATOR_CHANGED)
} else { } else {
@ -93,8 +94,8 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
// We may have already called notifyItemChanged before when checking // We may have already called notifyItemChanged before when checking
// if the item was being played, so in that case we don't need to // if the item was being played, so in that case we don't need to
// update again here. // update again here.
if (!updatedItem && item != null) { if (!updatedItem && music != null) {
val pos = currentList.indexOfFirst { it == item } val pos = currentList.indexOfFirst { it == music }
if (pos > -1) { if (pos > -1) {
notifyItemChanged(pos, PAYLOAD_PLAYING_INDICATOR_CHANGED) notifyItemChanged(pos, PAYLOAD_PLAYING_INDICATOR_CHANGED)
} else { } else {

View file

@ -26,10 +26,7 @@ import org.oxycblt.auxio.databinding.ItemParentBinding
import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
@ -45,7 +42,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
* @param song The new [Song] to bind. * @param song The new [Song] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
fun bind(song: Song, listener: SelectableListListener) { fun bind(song: Song, listener: SelectableListListener<Song>) {
listener.bind(song, this, menuButton = binding.songMenu) listener.bind(song, this, menuButton = binding.songMenu)
binding.songAlbumCover.bind(song) binding.songAlbumCover.bind(song)
binding.songName.text = song.resolveName(binding.context) binding.songName.text = song.resolveName(binding.context)
@ -92,7 +89,7 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding
* @param album The new [Album] to bind. * @param album The new [Album] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
fun bind(album: Album, listener: SelectableListListener) { fun bind(album: Album, listener: SelectableListListener<Album>) {
listener.bind(album, this, menuButton = binding.parentMenu) listener.bind(album, this, menuButton = binding.parentMenu)
binding.parentImage.bind(album) binding.parentImage.bind(album)
binding.parentName.text = album.resolveName(binding.context) binding.parentName.text = album.resolveName(binding.context)
@ -141,7 +138,7 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
* @param artist The new [Artist] to bind. * @param artist The new [Artist] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
fun bind(artist: Artist, listener: SelectableListListener) { fun bind(artist: Artist, listener: SelectableListListener<Artist>) {
listener.bind(artist, this, menuButton = binding.parentMenu) listener.bind(artist, this, menuButton = binding.parentMenu)
binding.parentImage.bind(artist) binding.parentImage.bind(artist)
binding.parentName.text = artist.resolveName(binding.context) binding.parentName.text = artist.resolveName(binding.context)
@ -200,7 +197,7 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding
* @param genre The new [Genre] to bind. * @param genre The new [Genre] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
fun bind(genre: Genre, listener: SelectableListListener) { fun bind(genre: Genre, listener: SelectableListListener<Genre>) {
listener.bind(genre, this, menuButton = binding.parentMenu) listener.bind(genre, this, menuButton = binding.parentMenu)
binding.parentImage.bind(genre) binding.parentImage.bind(genre)
binding.parentName.text = genre.resolveName(binding.context) binding.parentName.text = genre.resolveName(binding.context)

View file

@ -85,7 +85,8 @@ class MusicDirsDialog :
val dialog = it as AlertDialog val dialog = it as AlertDialog
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener { dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener {
logD("Opening launcher") logD("Opening launcher")
val launcher = requireNotNull(openDocumentTreeLauncher) { val launcher =
requireNotNull(openDocumentTreeLauncher) {
"Document tree launcher was not available" "Document tree launcher was not available"
} }

View file

@ -32,7 +32,7 @@ import org.oxycblt.auxio.util.inflater
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
* @author OxygenCobalt. * @author OxygenCobalt.
*/ */
class ArtistChoiceAdapter(private val listener: ClickableListListener) : class ArtistChoiceAdapter(private val listener: ClickableListListener<Artist>) :
RecyclerView.Adapter<ArtistChoiceViewHolder>() { RecyclerView.Adapter<ArtistChoiceViewHolder>() {
private var artists = listOf<Artist>() private var artists = listOf<Artist>()
@ -67,7 +67,7 @@ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
* @param artist The new [Artist] to bind. * @param artist The new [Artist] to bind.
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
*/ */
fun bind(artist: Artist, listener: ClickableListListener) { fun bind(artist: Artist, listener: ClickableListListener<Artist>) {
listener.bind(artist, this) listener.bind(artist, this)
binding.pickerImage.bind(artist) binding.pickerImage.bind(artist)
binding.pickerName.text = artist.resolveName(binding.context) binding.pickerName.text = artist.resolveName(binding.context)

View file

@ -22,7 +22,6 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
@ -41,9 +40,8 @@ class ArtistNavigationPickerDialog : ArtistPickerDialog() {
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) {
super.onClick(item, viewHolder) super.onClick(item, viewHolder)
check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" }
// User made a choice, navigate to it. // User made a choice, navigate to it.
navModel.exploreNavigateTo(item) navModel.exploreNavigateTo(item)
} }

View file

@ -26,7 +26,6 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -38,7 +37,7 @@ import org.oxycblt.auxio.util.collectImmediately
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class ArtistPickerDialog : abstract class ArtistPickerDialog :
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener { ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Artist> {
protected val pickerModel: PickerViewModel by viewModels() protected val pickerModel: PickerViewModel by viewModels()
// Okay to leak this since the Listener will not be called until after initialization. // Okay to leak this since the Listener will not be called until after initialization.
private val artistAdapter = ArtistChoiceAdapter(@Suppress("LeakingThis") this) private val artistAdapter = ArtistChoiceAdapter(@Suppress("LeakingThis") this)
@ -68,7 +67,7 @@ abstract class ArtistPickerDialog :
binding.pickerRecycler.adapter = null binding.pickerRecycler.adapter = null
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) {
findNavController().navigateUp() findNavController().navigateUp()
} }
} }

View file

@ -21,11 +21,12 @@ import android.os.Bundle
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.requireIs
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* An [ArtistPickerDialog] intended for when [Artist] playback is ambiguous. * An [ArtistPickerDialog] intended for when [Artist] playback is ambiguous.
@ -42,12 +43,10 @@ class ArtistPlaybackPickerDialog : ArtistPickerDialog() {
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) {
super.onClick(item, viewHolder) super.onClick(item, viewHolder)
// User made a choice, play the given song from that artist. // User made a choice, play the given song from that artist.
check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" } val song = requireIs<Song>(unlikelyToBeNull(pickerModel.currentItem.value))
val song = pickerModel.currentItem.value
check(song is Song) { "Unexpected datatype: ${item::class.simpleName}" }
playbackModel.playFromArtist(song, item) playbackModel.playFromArtist(song, item)
} }
} }

View file

@ -32,7 +32,7 @@ import org.oxycblt.auxio.util.inflater
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
* @author OxygenCobalt. * @author OxygenCobalt.
*/ */
class GenreChoiceAdapter(private val listener: ClickableListListener) : class GenreChoiceAdapter(private val listener: ClickableListListener<Genre>) :
RecyclerView.Adapter<GenreChoiceViewHolder>() { RecyclerView.Adapter<GenreChoiceViewHolder>() {
private var genres = listOf<Genre>() private var genres = listOf<Genre>()
@ -67,7 +67,7 @@ class GenreChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
* @param genre The new [Genre] to bind. * @param genre The new [Genre] to bind.
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
*/ */
fun bind(genre: Genre, listener: ClickableListListener) { fun bind(genre: Genre, listener: ClickableListListener<Genre>) {
listener.bind(genre, this) listener.bind(genre, this)
binding.pickerImage.bind(genre) binding.pickerImage.bind(genre)
binding.pickerName.text = genre.resolveName(binding.context) binding.pickerName.text = genre.resolveName(binding.context)

View file

@ -27,20 +27,21 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.Item
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.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.requireIs
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A picker [ViewBindingDialogFragment] intended for when [Genre] playback is ambiguous. * A picker [ViewBindingDialogFragment] intended for when [Genre] playback is ambiguous.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenrePlaybackPickerDialog : class GenrePlaybackPickerDialog :
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener { ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Genre> {
private val pickerModel: PickerViewModel by viewModels() private val pickerModel: PickerViewModel by viewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
// Information about what Song to show choices for is initially within the navigation arguments // Information about what Song to show choices for is initially within the navigation arguments
@ -75,11 +76,9 @@ class GenrePlaybackPickerDialog :
binding.pickerRecycler.adapter = null binding.pickerRecycler.adapter = null
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Genre, viewHolder: RecyclerView.ViewHolder) {
// User made a choice, play the given song from that genre. // User made a choice, play the given song from that genre.
check(item is Genre) { "Unexpected datatype: ${item::class.simpleName}" } val song = requireIs<Song>(unlikelyToBeNull(pickerModel.currentItem.value))
val song = pickerModel.currentItem.value
check(song is Song) { "Unexpected datatype: ${item::class.simpleName}" }
playbackModel.playFromGenre(song, item) playbackModel.playFromGenre(song, item)
} }
} }

View file

@ -41,7 +41,7 @@ import org.oxycblt.auxio.util.inflater
* @param listener A [EditableListListener] to bind interactions to. * @param listener A [EditableListListener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class QueueAdapter(private val listener: EditableListListener) : class QueueAdapter(private val listener: EditableListListener<Song>) :
RecyclerView.Adapter<QueueSongViewHolder>() { RecyclerView.Adapter<QueueSongViewHolder>() {
private var differ = SyncListDiffer(this, QueueSongViewHolder.DIFF_CALLBACK) private var differ = SyncListDiffer(this, QueueSongViewHolder.DIFF_CALLBACK)
// Since PlayingIndicator adapter relies on an item value, we cannot use it for this // Since PlayingIndicator adapter relies on an item value, we cannot use it for this
@ -178,7 +178,7 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
* @param listener A [EditableListListener] to bind interactions to. * @param listener A [EditableListListener] to bind interactions to.
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
fun bind(song: Song, listener: EditableListListener) { fun bind(song: Song, listener: EditableListListener<Song>) {
listener.bind(song, this, bodyView, binding.songDragHandle) listener.bind(song, this, bodyView, binding.songDragHandle)
binding.songAlbumCover.bind(song) binding.songAlbumCover.bind(song)
binding.songName.text = song.resolveName(binding.context) binding.songName.text = song.resolveName(binding.context)

View file

@ -27,7 +27,6 @@ import androidx.recyclerview.widget.RecyclerView
import kotlin.math.min import kotlin.math.min
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.EditableListListener
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
@ -39,7 +38,7 @@ import org.oxycblt.auxio.util.logD
* A [ViewBindingFragment] that displays an editable queue. * A [ViewBindingFragment] that displays an editable queue.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), EditableListListener { class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), EditableListListener<Song> {
private val queueModel: QueueViewModel by activityViewModels() private val queueModel: QueueViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val queueAdapter = QueueAdapter(this) private val queueAdapter = QueueAdapter(this)
@ -81,7 +80,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), EditableListL
binding.queueRecycler.adapter = null binding.queueRecycler.adapter = null
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Song, viewHolder: RecyclerView.ViewHolder) {
queueModel.goto(viewHolder.bindingAdapterPosition) queueModel.goto(viewHolder.bindingAdapterPosition)
} }

View file

@ -127,11 +127,9 @@ class ReplayGainAudioProcessor(private val context: Context) :
// User wants track gain to be preferred. Default to album gain only if // User wants track gain to be preferred. Default to album gain only if
// there is no track gain. // there is no track gain.
ReplayGainMode.TRACK -> gain.track == 0f ReplayGainMode.TRACK -> gain.track == 0f
// User wants album gain to be preferred. Default to track gain only if // User wants album gain to be preferred. Default to track gain only if
// here is no album gain. // here is no album gain.
ReplayGainMode.ALBUM -> gain.album != 0f ReplayGainMode.ALBUM -> gain.album != 0f
// User wants album gain to be used when in an album, track gain otherwise. // User wants album gain to be used when in an album, track gain otherwise.
ReplayGainMode.DYNAMIC -> ReplayGainMode.DYNAMIC ->
playbackManager.parent is Album && playbackManager.parent is Album &&

View file

@ -22,17 +22,14 @@ import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.list.* import org.oxycblt.auxio.list.*
import org.oxycblt.auxio.list.recycler.* import org.oxycblt.auxio.list.recycler.*
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
/** /**
* An adapter that displays search results. * An adapter that displays search results.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SearchAdapter(private val listener: SelectableListListener) : class SearchAdapter(private val listener: SelectableListListener<Music>) :
SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup { SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
private val differ = AsyncListDiffer(this, DIFF_CALLBACK) private val differ = AsyncListDiffer(this, DIFF_CALLBACK)

View file

@ -50,7 +50,7 @@ import org.oxycblt.auxio.util.*
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SearchFragment : ListFragment<FragmentSearchBinding>() { class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
private val searchModel: SearchViewModel by androidViewModels() private val searchModel: SearchViewModel by androidViewModels()
private val searchAdapter = SearchAdapter(this) private val searchAdapter = SearchAdapter(this)
private var imm: InputMethodManager? = null private var imm: InputMethodManager? = null
@ -134,26 +134,25 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
return false return false
} }
override fun onRealClick(music: Music) { override fun onRealClick(item: Music) {
when (music) { when (item) {
is Song -> is Song ->
when (Settings(requireContext()).libPlaybackMode) { when (Settings(requireContext()).libPlaybackMode) {
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> playbackModel.playFromGenre(music) MusicMode.GENRES -> playbackModel.playFromGenre(item)
} }
is MusicParent -> navModel.exploreNavigateTo(music) is MusicParent -> navModel.exploreNavigateTo(item)
} }
} }
override fun onOpenMenu(item: Item, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Song -> openMusicMenu(anchor, R.menu.menu_song_actions, item) is Song -> openMusicMenu(anchor, R.menu.menu_song_actions, item)
is Album -> openMusicMenu(anchor, R.menu.menu_album_actions, item) is Album -> openMusicMenu(anchor, R.menu.menu_album_actions, item)
is Artist -> openMusicMenu(anchor, R.menu.menu_artist_actions, item) is Artist -> openMusicMenu(anchor, R.menu.menu_artist_actions, item)
is Genre -> openMusicMenu(anchor, R.menu.menu_artist_actions, item) is Genre -> openMusicMenu(anchor, R.menu.menu_artist_actions, item)
else -> logW("Unexpected datatype when opening menu: ${item::class.java}")
} }
} }

View file

@ -79,15 +79,15 @@ class NavigationViewModel : ViewModel() {
/** /**
* Navigate to a given [Music] item. Will do nothing if already navigating. * Navigate to a given [Music] item. Will do nothing if already navigating.
* @param item The [Music] to navigate to. * @param music The [Music] to navigate to.
*/ */
fun exploreNavigateTo(item: Music) { fun exploreNavigateTo(music: Music) {
if (_exploreNavigationItem.value != null) { if (_exploreNavigationItem.value != null) {
logD("Already navigating, not doing explore action") logD("Already navigating, not doing explore action")
return return
} }
logD("Navigating to ${item.rawName}") logD("Navigating to ${music.rawName}")
_exploreNavigationItem.value = item _exploreNavigationItem.value = music
} }
/** /**

View file

@ -19,7 +19,6 @@ package org.oxycblt.auxio.ui.accent
import android.os.Build import android.os.Build
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
private val ACCENT_NAMES = private val ACCENT_NAMES =
@ -112,7 +111,7 @@ private val ACCENT_PRIMARY_COLORS =
* @param index The unique number for this particular accent. * @param index The unique number for this particular accent.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class Accent private constructor(val index: Int) : Item { class Accent private constructor(val index: Int) {
/** The name of this [Accent]. */ /** The name of this [Accent]. */
val name: Int val name: Int
get() = ACCENT_NAMES[index] get() = ACCENT_NAMES[index]

View file

@ -33,7 +33,7 @@ import org.oxycblt.auxio.util.inflater
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AccentAdapter(private val listener: ClickableListListener) : class AccentAdapter(private val listener: ClickableListListener<Accent>) :
RecyclerView.Adapter<AccentViewHolder>() { RecyclerView.Adapter<AccentViewHolder>() {
/** The currently selected [Accent]. */ /** The currently selected [Accent]. */
var selectedAccent: Accent? = null var selectedAccent: Accent? = null
@ -93,7 +93,7 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
* @param accent The new [Accent] to bind. * @param accent The new [Accent] to bind.
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
*/ */
fun bind(accent: Accent, listener: ClickableListListener) { fun bind(accent: Accent, listener: ClickableListListener<Accent>) {
listener.bind(accent, this, binding.accent) listener.bind(accent, this, binding.accent)
binding.accent.apply { binding.accent.apply {
// Add a Tooltip based on the content description so that the purpose of this // Add a Tooltip based on the content description so that the purpose of this

View file

@ -25,7 +25,6 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogAccentBinding import org.oxycblt.auxio.databinding.DialogAccentBinding
import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -36,7 +35,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AccentCustomizeDialog : class AccentCustomizeDialog :
ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener { ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener<Accent> {
private var accentAdapter = AccentAdapter(this) private var accentAdapter = AccentAdapter(this)
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater) override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
@ -80,8 +79,7 @@ class AccentCustomizeDialog :
binding.accentRecycler.adapter = null binding.accentRecycler.adapter = null
} }
override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Accent, viewHolder: RecyclerView.ViewHolder) {
check(item is Accent) { "Unexpected datatype: ${item::class.java}" }
accentAdapter.setSelectedAccent(item) accentAdapter.setSelectedAccent(item)
} }

View file

@ -33,6 +33,17 @@ fun <T> unlikelyToBeNull(value: T?) =
value!! value!!
} }
/**
* Require that the given data is a specific type [T].
* @param data The data to check.
* @return A data casted to [T].
* @throws IllegalStateException If the data cannot be casted to [T].
*/
inline fun <reified T> requireIs(data: Any): T {
check(data is T) { "Unexpected datatype: ${data::class.simpleName}" }
return data
}
/** /**
* Aliases a check to ensure that the given number is non-zero. * Aliases a check to ensure that the given number is non-zero.
* @return The given number if it's non-zero, null otherwise. * @return The given number if it's non-zero, null otherwise.