Create parent abstraction

Create a BaseModel variant for albums, artists, and genres to simplify on alot of code that should only run on those.
This commit is contained in:
OxygenCobalt 2021-01-30 11:18:29 -07:00
parent ec310a5b93
commit 67c177ccf3
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 79 additions and 104 deletions

View file

@ -5,22 +5,22 @@ import android.view.ViewGroup
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
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
/** /**
* An adapter for displaying library items. * An adapter for displaying library items. Supports [Parent]s only.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class LibraryAdapter( class LibraryAdapter(
private val doOnClick: (data: BaseModel) -> Unit, private val doOnClick: (data: Parent) -> Unit,
private val doOnLongClick: (view: View, data: BaseModel) -> Unit private val doOnLongClick: (view: View, data: Parent) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var data = listOf<BaseModel>() private var data = listOf<Parent>()
override fun getItemCount(): Int = data.size override fun getItemCount(): Int = data.size
@ -29,8 +29,6 @@ class LibraryAdapter(
is Genre -> GenreViewHolder.ITEM_TYPE is Genre -> GenreViewHolder.ITEM_TYPE
is Artist -> ArtistViewHolder.ITEM_TYPE is Artist -> ArtistViewHolder.ITEM_TYPE
is Album -> AlbumViewHolder.ITEM_TYPE is Album -> AlbumViewHolder.ITEM_TYPE
else -> -1
} }
} }
@ -64,7 +62,7 @@ class LibraryAdapter(
* Update the data directly. [notifyDataSetChanged] will be called * Update the data directly. [notifyDataSetChanged] will be called
* @param newData The new data to be used * @param newData The new data to be used
*/ */
fun updateData(newData: List<BaseModel>) { fun updateData(newData: List<Parent>) {
data = newData data = newData
notifyDataSetChanged() notifyDataSetChanged()

View file

@ -12,11 +12,10 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLibraryBinding import org.oxycblt.auxio.databinding.FragmentLibraryBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.fixAnimInfoLeak import org.oxycblt.auxio.ui.fixAnimInfoLeak
@ -81,10 +80,10 @@ class LibraryFragment : Fragment() {
if (it != null) { if (it != null) {
libraryModel.updateNavigationStatus(false) libraryModel.updateNavigationStatus(false)
if (it is Song) { if (it is Parent) {
onItemSelection(it.album)
} else {
onItemSelection(it) onItemSelection(it)
} else if (it is Song) {
onItemSelection(it.album)
} }
} }
} }
@ -107,34 +106,22 @@ class LibraryFragment : Fragment() {
} }
/** /**
* Navigate to an item * Navigate to a parent UI
* @param baseModel The item that should be navigated to. * @param parent The parent that should be navigated with
*/ */
private fun onItemSelection(baseModel: BaseModel) { private fun onItemSelection(parent: Parent) {
if (baseModel is Song) {
logE("onItemSelection does not support songs")
return
}
requireView().rootView.clearFocus() requireView().rootView.clearFocus()
if (!libraryModel.isNavigating) { if (!libraryModel.isNavigating) {
libraryModel.updateNavigationStatus(true) libraryModel.updateNavigationStatus(true)
logD("Navigating to the detail fragment for ${baseModel.name}") logD("Navigating to the detail fragment for ${parent.name}")
findNavController().navigate( findNavController().navigate(
when (baseModel) { when (parent) {
is Genre -> LibraryFragmentDirections.actionShowGenre(baseModel.id) is Genre -> LibraryFragmentDirections.actionShowGenre(parent.id)
is Artist -> LibraryFragmentDirections.actionShowArtist(baseModel.id) is Artist -> LibraryFragmentDirections.actionShowArtist(parent.id)
is Album -> LibraryFragmentDirections.actionShowAlbum(baseModel.id) is Album -> LibraryFragmentDirections.actionShowAlbum(parent.id)
// If given model wasn't valid, then reset the navigation status
// and abort the navigation.
else -> {
libraryModel.updateNavigationStatus(false)
return
}
} }
) )
} }

View file

@ -5,8 +5,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.recycler.DisplayMode import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.recycler.SortMode import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
@ -17,8 +17,8 @@ import org.oxycblt.auxio.settings.SettingsManager
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class LibraryViewModel : ViewModel(), SettingsManager.Callback { class LibraryViewModel : ViewModel(), SettingsManager.Callback {
private val mLibraryData = MutableLiveData(listOf<BaseModel>()) private val mLibraryData = MutableLiveData(listOf<Parent>())
val libraryData: LiveData<List<BaseModel>> get() = mLibraryData val libraryData: LiveData<List<Parent>> get() = mLibraryData
private var mIsNavigating = false private var mIsNavigating = false
val isNavigating: Boolean get() = mIsNavigating val isNavigating: Boolean get() = mIsNavigating
@ -91,17 +91,11 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
*/ */
private fun updateLibraryData() { private fun updateLibraryData() {
mLibraryData.value = when (mDisplayMode) { mLibraryData.value = when (mDisplayMode) {
DisplayMode.SHOW_GENRES -> { DisplayMode.SHOW_GENRES -> mSortMode.getSortedGenreList(musicStore.genres)
mSortMode.getSortedGenreList(musicStore.genres)
}
DisplayMode.SHOW_ARTISTS -> { DisplayMode.SHOW_ARTISTS -> mSortMode.getSortedArtistList(musicStore.artists)
mSortMode.getSortedBaseModelList(musicStore.artists)
}
DisplayMode.SHOW_ALBUMS -> { DisplayMode.SHOW_ALBUMS -> mSortMode.getSortedAlbumList(musicStore.albums)
mSortMode.getSortedAlbumList(musicStore.albums)
}
else -> error("DisplayMode $mDisplayMode is unsupported.") else -> error("DisplayMode $mDisplayMode is unsupported.")
} }

View file

@ -15,6 +15,12 @@ sealed class BaseModel {
abstract val name: String abstract val name: String
} }
/**
* [BaseModel] variant that denotes that this object is a parent of other data objects, such
* as an [Album] or [Artist]
*/
sealed class Parent : BaseModel()
/** /**
* The data object for a song. Inherits [BaseModel]. * The data object for a song. Inherits [BaseModel].
* @property albumId The Song's Album ID. Never use this outside of when attaching a song to its album. * @property albumId The Song's Album ID. Never use this outside of when attaching a song to its album.
@ -72,7 +78,7 @@ data class Song(
} }
/** /**
* The data object for an album. Inherits [BaseModel]. * The data object for an album. Inherits [Parent].
* @property artistName The name of the parent artist. Do not use this outside of creating the artist from albums * @property artistName The name of the parent artist. Do not use this outside of creating the artist from albums
* @property coverUri The [Uri] for the album's cover. **Load this using Coil.** * @property coverUri The [Uri] for the album's cover. **Load this using Coil.**
* @property year The year this album was released. 0 if there is none in the metadata. * @property year The year this album was released. 0 if there is none in the metadata.
@ -87,7 +93,7 @@ data class Album(
val artistName: String, val artistName: String,
val coverUri: Uri = Uri.EMPTY, val coverUri: Uri = Uri.EMPTY,
val year: Int = 0 val year: Int = 0
) : BaseModel() { ) : Parent() {
private var mArtist: Artist? = null private var mArtist: Artist? = null
val artist: Artist get() { val artist: Artist get() {
val artist = mArtist val artist = mArtist
@ -123,7 +129,7 @@ data class Album(
} }
/** /**
* The data object for an artist. Inherits [BaseModel] * The data object for an artist. Inherits [Parent]
* @property albums The list of all [Album]s in this artist * @property albums The list of all [Album]s in this artist
* @property genre The most prominent genre for this artist * @property genre The most prominent genre for this artist
* @property songs The list of all [Song]s in this artist * @property songs The list of all [Song]s in this artist
@ -133,7 +139,7 @@ data class Artist(
override val id: Long = -1, override val id: Long = -1,
override val name: String, override val name: String,
val albums: List<Album> val albums: List<Album>
) : BaseModel() { ) : Parent() {
init { init {
albums.forEach { albums.forEach {
it.applyArtist(this) it.applyArtist(this)
@ -158,7 +164,7 @@ data class Artist(
} }
/** /**
* The data object for a genre. Inherits [BaseModel] * The data object for a genre. Inherits [Parent]
* @property songs The list of all [Song]s in this genre. * @property songs The list of all [Song]s in this genre.
* @property displayName A name that can be displayed without it showing up as an integer. ***USE THIS INSTEAD OF [name]!!!!*** * @property displayName A name that can be displayed without it showing up as an integer. ***USE THIS INSTEAD OF [name]!!!!***
* @author OxygenCobalt * @author OxygenCobalt
@ -166,7 +172,7 @@ data class Artist(
data class Genre( data class Genre(
override val id: Long = -1, override val id: Long = -1,
override val name: String, override val name: String,
) : BaseModel() { ) : Parent() {
private val mSongs = mutableListOf<Song>() private val mSongs = mutableListOf<Song>()
val songs: List<Song> get() = mSongs val songs: List<Song> get() = mSongs

View file

@ -25,8 +25,8 @@ class MusicStore private constructor() {
val songs: List<Song> get() = mSongs val songs: List<Song> get() = mSongs
/** All parent models (ex Albums, Artists) loaded by Auxio */ /** All parent models (ex Albums, Artists) loaded by Auxio */
val parents: MutableList<BaseModel> by lazy { val parents: MutableList<Parent> by lazy {
mutableListOf<BaseModel>().apply { mutableListOf<Parent>().apply {
addAll(mGenres) addAll(mGenres)
addAll(mArtists) addAll(mArtists)
addAll(mAlbums) addAll(mAlbums)

View file

@ -12,8 +12,8 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toDuration import org.oxycblt.auxio.music.toDuration
import org.oxycblt.auxio.playback.queue.QueueAdapter import org.oxycblt.auxio.playback.queue.QueueAdapter
@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.createToast
class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// Playback // Playback
private val mSong = MutableLiveData<Song?>() private val mSong = MutableLiveData<Song?>()
private val mParent = MutableLiveData<BaseModel?>() private val mParent = MutableLiveData<Parent?>()
private val mPosition = MutableLiveData(0L) private val mPosition = MutableLiveData(0L)
// Queue // Queue
@ -56,7 +56,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
/** The current song. */ /** The current song. */
val song: LiveData<Song?> get() = mSong val song: LiveData<Song?> get() = mSong
/** The current model that is being played from, such as an [Album] or [Artist] */ /** The current model that is being played from, such as an [Album] or [Artist] */
val parent: LiveData<BaseModel?> get() = mParent val parent: LiveData<Parent?> get() = mParent
/** The current playback position, in seconds */ /** The current playback position, in seconds */
val position: LiveData<Long> get() = mPosition val position: LiveData<Long> get() = mPosition
@ -125,7 +125,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
return return
} }
playbackManager.playParentModel(album, shuffled) playbackManager.playParent(album, shuffled)
} }
/** Play an Artist */ /** Play an Artist */
@ -136,7 +136,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
return return
} }
playbackManager.playParentModel(artist, shuffled) playbackManager.playParent(artist, shuffled)
} }
/** Play a genre. */ /** Play a genre. */
@ -147,7 +147,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
return return
} }
playbackManager.playParentModel(genre, shuffled) playbackManager.playParent(genre, shuffled)
} }
/** Shuffle all songs */ /** Shuffle all songs */
@ -365,7 +365,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
mSong.value = song mSong.value = song
} }
override fun onParentUpdate(parent: BaseModel?) { override fun onParentUpdate(parent: Parent?) {
mParent.value = parent mParent.value = parent
} }

View file

@ -10,10 +10,9 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.SortMode import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
@ -42,7 +41,7 @@ class PlaybackStateManager private constructor() {
field = value field = value
callbacks.forEach { it.onPositionUpdate(value) } callbacks.forEach { it.onPositionUpdate(value) }
} }
private var mParent: BaseModel? = null private var mParent: Parent? = null
set(value) { set(value) {
field = value field = value
callbacks.forEach { it.onParentUpdate(value) } callbacks.forEach { it.onParentUpdate(value) }
@ -98,7 +97,7 @@ class PlaybackStateManager private constructor() {
/** The currently playing song. Null if there isn't one */ /** The currently playing song. Null if there isn't one */
val song: Song? get() = mSong val song: Song? get() = mSong
/** The parent the queue is based on, null if all_songs */ /** The parent the queue is based on, null if all_songs */
val parent: BaseModel? get() = mParent val parent: Parent? get() = mParent
/** The current playback progress */ /** The current playback progress */
val position: Long get() = mPosition val position: Long get() = mPosition
/** The current queue determined by [parent] and [mode] */ /** The current queue determined by [parent] and [mode] */
@ -192,33 +191,26 @@ class PlaybackStateManager private constructor() {
/** /**
* Play a parent model, e.g an artist or an album. * Play a parent model, e.g an artist or an album.
* @param baseModel The model to use * @param parent The model to use
* @param shuffled Whether to shuffle the queue or not * @param shuffled Whether to shuffle the queue or not
*/ */
fun playParentModel(baseModel: BaseModel, shuffled: Boolean) { fun playParent(parent: Parent, shuffled: Boolean) {
if (baseModel is Song || baseModel is Header) { logD("Playing ${parent.name}")
// This should never occur.
logE("playParentModel is not meant to play ${baseModel::class.simpleName}.")
return mParent = parent
}
logD("Playing ${baseModel.name}")
mParent = baseModel
mIndex = 0 mIndex = 0
when (baseModel) { when (parent) {
is Album -> { is Album -> {
mQueue = baseModel.songs.toMutableList() mQueue = parent.songs.toMutableList()
mMode = PlaybackMode.IN_ALBUM mMode = PlaybackMode.IN_ALBUM
} }
is Artist -> { is Artist -> {
mQueue = baseModel.songs.toMutableList() mQueue = parent.songs.toMutableList()
mMode = PlaybackMode.IN_ARTIST mMode = PlaybackMode.IN_ARTIST
} }
is Genre -> { is Genre -> {
mQueue = baseModel.songs.toMutableList() mQueue = parent.songs.toMutableList()
mMode = PlaybackMode.IN_GENRE mMode = PlaybackMode.IN_GENRE
} }
@ -817,7 +809,7 @@ class PlaybackStateManager private constructor() {
*/ */
interface Callback { interface Callback {
fun onSongUpdate(song: Song?) {} fun onSongUpdate(song: Song?) {}
fun onParentUpdate(parent: BaseModel?) {} fun onParentUpdate(parent: Parent?) {}
fun onPositionUpdate(position: Long) {} fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: MutableList<Song>) {} fun onQueueUpdate(queue: MutableList<Song>) {}
fun onUserQueueUpdate(userQueue: MutableList<Song>) {} fun onUserQueueUpdate(userQueue: MutableList<Song>) {}

View file

@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.IdRes import androidx.annotation.IdRes
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -25,8 +26,8 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
/** /**
* Get a sorted list of genres for a SortMode. Only supports alphabetic sorting. * Get a sorted list of genres for a SortMode. Only supports alphabetic sorting.
* @param genres An unsorted list of artists. * @param genres An unsorted list of genres.
* @return The sorted list of artists. * @return The sorted list of genres.
*/ */
fun getSortedGenreList(genres: List<Genre>): List<Genre> { fun getSortedGenreList(genres: List<Genre>): List<Genre> {
return when (this) { return when (this) {
@ -42,6 +43,25 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
} }
} }
/**
* Get a sorted list of artists for a SortMode. Only supports alphabetic sorting.
* @param artists An unsorted list of artists.
* @return The sorted list of artists.
*/
fun getSortedArtistList(artists: List<Artist>): List<Artist> {
return when (this) {
ALPHA_UP -> artists.sortedWith(
compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }
)
ALPHA_DOWN -> artists.sortedWith(
compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }
)
else -> artists
}
}
/** /**
* Get a sorted list of albums for a SortMode. Supports alpha + numeric sorting. * Get a sorted list of albums for a SortMode. Supports alpha + numeric sorting.
* @param albums An unsorted list of albums. * @param albums An unsorted list of albums.
@ -124,28 +144,6 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
} }
} }
/**
* Get a sorted list of BaseModels. Supports alpha + numeric sorting.
* @param baseModels An unsorted list of BaseModels.
* @return The sorted list of BaseModels.
*/
fun getSortedBaseModelList(baseModels: List<BaseModel>): List<BaseModel> {
return when (this) {
ALPHA_UP -> baseModels.sortedWith(
compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }
)
ALPHA_DOWN -> baseModels.sortedWith(
compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }
)
NUMERIC_UP -> baseModels.sortedWith(compareByDescending { it.id })
NUMERIC_DOWN -> baseModels.sortedWith(compareBy { it.id })
else -> baseModels
}
}
/** /**
* Get a sorting menu ID for this mode. Alphabetic only. * Get a sorting menu ID for this mode. Alphabetic only.
* @return The action id for this mode. * @return The action id for this mode.