playback: mostly hide playback mode details
Mostly hide the code that handles starting playback based on a given mode into their respective ViewModels. Again, makes testing easier.
This commit is contained in:
parent
ac9f50c0a0
commit
6fa53ab873
16 changed files with 96 additions and 69 deletions
|
@ -11,6 +11,7 @@
|
||||||
- Fixed crash that would occur in music folders dialog when user does not have a working
|
- Fixed crash that would occur in music folders dialog when user does not have a working
|
||||||
file manager
|
file manager
|
||||||
- Fixed notification not updating due to settings changes
|
- Fixed notification not updating due to settings changes
|
||||||
|
- Fixed genre picker from repeatedly showing up when device rotates
|
||||||
|
|
||||||
#### What's Changed
|
#### What's Changed
|
||||||
- Implemented new queue system
|
- Implemented new queue system
|
||||||
|
|
|
@ -309,7 +309,7 @@ class MainFragment :
|
||||||
navModel.mainNavigateTo(
|
navModel.mainNavigateTo(
|
||||||
MainNavigationAction.Directions(
|
MainNavigationAction.Directions(
|
||||||
MainFragmentDirections.actionPickPlaybackGenre(song.uid)))
|
MainFragmentDirections.actionPickPlaybackGenre(song.uid)))
|
||||||
playbackModel.finishPlaybackArtistPicker()
|
playbackModel.finishPlaybackGenrePicker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ import org.oxycblt.auxio.util.*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class AlbumDetailFragment :
|
class AlbumDetailFragment :
|
||||||
ListFragment<Music, FragmentDetailBinding>(), AlbumDetailAdapter.Listener {
|
ListFragment<Song, 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.
|
||||||
|
@ -121,21 +121,12 @@ class AlbumDetailFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRealClick(item: Music) {
|
override fun onRealClick(item: Song) {
|
||||||
val song = requireIs<Song>(item)
|
// There can only be one album, so a null mode and an ALBUMS mode will function the same.
|
||||||
when (PlaybackSettings.from(requireContext()).inParentPlaybackMode) {
|
playbackModel.playFrom(item, detailModel.playbackMode ?: MusicMode.ALBUMS)
|
||||||
// "Play from shown item" and "Play from album" functionally have the same
|
|
||||||
// behavior since a song can only have one album.
|
|
||||||
null,
|
|
||||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(song)
|
|
||||||
MusicMode.SONGS -> playbackModel.playFromAll(song)
|
|
||||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(song)
|
|
||||||
MusicMode.GENRES -> playbackModel.playFromGenre(song)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenMenu(item: Music, anchor: View) {
|
override fun onOpenMenu(item: Song, anchor: View) {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,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<Music, FragmentDetailBinding>(), DetailAdapter.Listener {
|
class ArtistDetailFragment : ListFragment<Music, FragmentDetailBinding>(), DetailAdapter.Listener<Music> {
|
||||||
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.
|
||||||
|
@ -122,20 +122,18 @@ class ArtistDetailFragment : ListFragment<Music, FragmentDetailBinding>(), Detai
|
||||||
|
|
||||||
override fun onRealClick(item: Music) {
|
override fun onRealClick(item: Music) {
|
||||||
when (item) {
|
when (item) {
|
||||||
|
is Album -> navModel.exploreNavigateTo(item)
|
||||||
is Song -> {
|
is Song -> {
|
||||||
when (PlaybackSettings.from(requireContext()).inParentPlaybackMode) {
|
val playbackMode = detailModel.playbackMode
|
||||||
|
if (playbackMode != null) {
|
||||||
|
playbackModel.playFrom(item, playbackMode)
|
||||||
|
} else {
|
||||||
// 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 ->
|
playbackModel.playFromArtist(item,
|
||||||
playbackModel.playFromArtist(
|
unlikelyToBeNull(detailModel.currentArtist.value))
|
||||||
item, unlikelyToBeNull(detailModel.currentArtist.value))
|
|
||||||
MusicMode.SONGS -> playbackModel.playFromAll(item)
|
|
||||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
|
|
||||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
|
|
||||||
MusicMode.GENRES -> playbackModel.playFromGenre(item)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Album -> navModel.exploreNavigateTo(item)
|
|
||||||
else -> error("Unexpected datatype: ${item::class.simpleName}")
|
else -> error("Unexpected datatype: ${item::class.simpleName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.oxycblt.auxio.music.Library
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Sort
|
import org.oxycblt.auxio.music.Sort
|
||||||
import org.oxycblt.auxio.music.storage.MimeType
|
import org.oxycblt.auxio.music.storage.MimeType
|
||||||
|
import org.oxycblt.auxio.playback.PlaybackSettings
|
||||||
import org.oxycblt.auxio.util.*
|
import org.oxycblt.auxio.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +51,7 @@ class DetailViewModel(application: Application) :
|
||||||
AndroidViewModel(application), MusicStore.Listener {
|
AndroidViewModel(application), MusicStore.Listener {
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
private val musicSettings = MusicSettings.from(application)
|
private val musicSettings = MusicSettings.from(application)
|
||||||
|
private val playbackSettings = PlaybackSettings.from(application)
|
||||||
|
|
||||||
private var currentSongJob: Job? = null
|
private var currentSongJob: Job? = null
|
||||||
|
|
||||||
|
@ -125,6 +127,12 @@ class DetailViewModel(application: Application) :
|
||||||
currentGenre.value?.let(::refreshGenreList)
|
currentGenre.value?.let(::refreshGenreList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [MusicMode] to use when playing a [Song] from the UI, or null to play from the currently
|
||||||
|
* shown item.
|
||||||
|
*/
|
||||||
|
val playbackMode: MusicMode? get() = playbackSettings.inParentPlaybackMode
|
||||||
|
|
||||||
init {
|
init {
|
||||||
musicStore.addListener(this)
|
musicStore.addListener(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,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<Music, FragmentDetailBinding>(), DetailAdapter.Listener {
|
class GenreDetailFragment : ListFragment<Music, FragmentDetailBinding>(), DetailAdapter.Listener<Music> {
|
||||||
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.
|
||||||
|
@ -122,18 +122,17 @@ class GenreDetailFragment : ListFragment<Music, FragmentDetailBinding>(), Detail
|
||||||
override fun onRealClick(item: Music) {
|
override fun onRealClick(item: Music) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is Artist -> navModel.exploreNavigateTo(item)
|
is Artist -> navModel.exploreNavigateTo(item)
|
||||||
is Song ->
|
is Song -> {
|
||||||
when (PlaybackSettings.from(requireContext()).inParentPlaybackMode) {
|
val playbackMode = detailModel.playbackMode
|
||||||
// When configured to play from the selected item, we already have a Genre
|
if (playbackMode != null) {
|
||||||
|
playbackModel.playFrom(item, playbackMode)
|
||||||
|
} else {
|
||||||
|
// When configured to play from the selected item, we already have an Artist
|
||||||
// to play from.
|
// to play from.
|
||||||
null ->
|
playbackModel.playFromArtist(item,
|
||||||
playbackModel.playFromGenre(
|
unlikelyToBeNull(detailModel.currentArtist.value))
|
||||||
item, unlikelyToBeNull(detailModel.currentGenre.value))
|
|
||||||
MusicMode.SONGS -> playbackModel.playFromAll(item)
|
|
||||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
|
|
||||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
|
|
||||||
MusicMode.GENRES -> playbackModel.playFromGenre(item)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else -> error("Unexpected datatype: ${item::class.simpleName}")
|
else -> error("Unexpected datatype: ${item::class.simpleName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene
|
||||||
* An extension to [DetailAdapter.Listener] that enables interactions specific to the album
|
* An extension to [DetailAdapter.Listener] that enables interactions specific to the album
|
||||||
* detail view.
|
* detail view.
|
||||||
*/
|
*/
|
||||||
interface Listener : DetailAdapter.Listener {
|
interface Listener : DetailAdapter.Listener<Song> {
|
||||||
/**
|
/**
|
||||||
* Called when the artist name in the [Album] header was clicked, requesting navigation to
|
* Called when the artist name in the [Album] header was clicked, requesting navigation to
|
||||||
* it's parent artist.
|
* it's parent artist.
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
||||||
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
||||||
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.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
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
|
||||||
|
@ -42,7 +43,7 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class ArtistDetailAdapter(private val listener: Listener) : DetailAdapter(listener, DIFF_CALLBACK) {
|
class ArtistDetailAdapter(private val listener: Listener<Music>) : DetailAdapter(listener, DIFF_CALLBACK) {
|
||||||
override fun getItemViewType(position: Int) =
|
override fun getItemViewType(position: Int) =
|
||||||
when (differ.currentList[position]) {
|
when (differ.currentList[position]) {
|
||||||
// Support an artist header, and special artist albums/songs.
|
// Support an artist header, and special artist albums/songs.
|
||||||
|
@ -109,7 +110,7 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
|
||||||
* @param artist The new [Artist] to bind.
|
* @param artist The new [Artist] to bind.
|
||||||
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
||||||
*/
|
*/
|
||||||
fun bind(artist: Artist, listener: DetailAdapter.Listener) {
|
fun bind(artist: Artist, listener: DetailAdapter.Listener<*>) {
|
||||||
binding.detailCover.bind(artist)
|
binding.detailCover.bind(artist)
|
||||||
binding.detailType.text = binding.context.getString(R.string.lbl_artist)
|
binding.detailType.text = binding.context.getString(R.string.lbl_artist)
|
||||||
binding.detailName.text = artist.resolveName(binding.context)
|
binding.detailName.text = artist.resolveName(binding.context)
|
||||||
|
|
|
@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
abstract class DetailAdapter(
|
abstract class DetailAdapter(
|
||||||
private val listener: Listener,
|
private val listener: Listener<*>,
|
||||||
itemCallback: DiffUtil.ItemCallback<Item>
|
itemCallback: DiffUtil.ItemCallback<Item>
|
||||||
) : SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
|
) : SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
|
||||||
// Safe to leak this since the listener will not fire during initialization
|
// Safe to leak this since the listener will not fire during initialization
|
||||||
|
@ -89,7 +89,7 @@ abstract class DetailAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An extended [SelectableListListener] for [DetailAdapter] implementations. */
|
/** An extended [SelectableListListener] for [DetailAdapter] implementations. */
|
||||||
interface Listener : SelectableListListener<Music> {
|
interface Listener<in T : Music> : SelectableListListener<T> {
|
||||||
// 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
|
||||||
|
@ -139,7 +139,7 @@ private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
|
||||||
* @param sortHeader The new [SortHeader] to bind.
|
* @param sortHeader The new [SortHeader] to bind.
|
||||||
* @param listener An [DetailAdapter.Listener] to bind interactions to.
|
* @param listener An [DetailAdapter.Listener] to bind interactions to.
|
||||||
*/
|
*/
|
||||||
fun bind(sortHeader: SortHeader, listener: DetailAdapter.Listener) {
|
fun bind(sortHeader: SortHeader, listener: DetailAdapter.Listener<*>) {
|
||||||
binding.headerTitle.text = binding.context.getString(sortHeader.titleRes)
|
binding.headerTitle.text = binding.context.getString(sortHeader.titleRes)
|
||||||
binding.headerButton.apply {
|
binding.headerButton.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
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.list.recycler.SongViewHolder
|
import org.oxycblt.auxio.list.recycler.SongViewHolder
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
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
|
||||||
|
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.inflater
|
||||||
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class GenreDetailAdapter(private val listener: Listener) : DetailAdapter(listener, DIFF_CALLBACK) {
|
class GenreDetailAdapter(private val listener: Listener<Music>) : DetailAdapter(listener, DIFF_CALLBACK) {
|
||||||
override fun getItemViewType(position: Int) =
|
override fun getItemViewType(position: Int) =
|
||||||
when (differ.currentList[position]) {
|
when (differ.currentList[position]) {
|
||||||
// Support the Genre header and generic Artist/Song items. There's nothing about
|
// Support the Genre header and generic Artist/Song items. There's nothing about
|
||||||
|
@ -105,7 +106,7 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite
|
||||||
* @param genre The new [Song] to bind.
|
* @param genre The new [Song] to bind.
|
||||||
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
* @param listener A [DetailAdapter.Listener] to bind interactions to.
|
||||||
*/
|
*/
|
||||||
fun bind(genre: Genre, listener: DetailAdapter.Listener) {
|
fun bind(genre: Genre, listener: DetailAdapter.Listener<*>) {
|
||||||
binding.detailCover.bind(genre)
|
binding.detailCover.bind(genre)
|
||||||
binding.detailType.text = binding.context.getString(R.string.lbl_genre)
|
binding.detailType.text = binding.context.getString(R.string.lbl_genre)
|
||||||
binding.detailName.text = genre.resolveName(binding.context)
|
binding.detailName.text = genre.resolveName(binding.context)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.oxycblt.auxio.music.*
|
||||||
import org.oxycblt.auxio.music.Library
|
import org.oxycblt.auxio.music.Library
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Sort
|
import org.oxycblt.auxio.music.Sort
|
||||||
|
import org.oxycblt.auxio.playback.PlaybackSettings
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,6 +38,7 @@ class HomeViewModel(application: Application) :
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
private val homeSettings = HomeSettings.from(application)
|
private val homeSettings = HomeSettings.from(application)
|
||||||
private val musicSettings = MusicSettings.from(application)
|
private val musicSettings = MusicSettings.from(application)
|
||||||
|
private val playbackSettings = PlaybackSettings.from(application)
|
||||||
|
|
||||||
private val _songsList = MutableStateFlow(listOf<Song>())
|
private val _songsList = MutableStateFlow(listOf<Song>())
|
||||||
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
|
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
|
||||||
|
@ -62,11 +64,14 @@ class HomeViewModel(application: Application) :
|
||||||
val genresList: StateFlow<List<Genre>>
|
val genresList: StateFlow<List<Genre>>
|
||||||
get() = _genresList
|
get() = _genresList
|
||||||
|
|
||||||
|
/** The [MusicMode] to use when playing a [Song] from the UI. */
|
||||||
|
val playbackMode: MusicMode get() = playbackSettings.inListPlaybackMode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of [MusicMode] corresponding to the current [Tab] configuration, excluding invisible
|
* A list of [MusicMode] corresponding to the current [Tab] configuration, excluding invisible
|
||||||
* [Tab]s.
|
* [Tab]s.
|
||||||
*/
|
*/
|
||||||
var currentTabModes: List<MusicMode> = makeTabModes()
|
var currentTabModes = makeTabModes()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val _currentTabMode = MutableStateFlow(currentTabModes[0])
|
private val _currentTabMode = MutableStateFlow(currentTabModes[0])
|
||||||
|
|
|
@ -130,12 +130,7 @@ class SongListFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRealClick(item: Song) {
|
override fun onRealClick(item: Song) {
|
||||||
when (PlaybackSettings.from(requireContext()).inListPlaybackMode) {
|
playbackModel.playFrom(item, homeModel.playbackMode)
|
||||||
MusicMode.SONGS -> playbackModel.playFromAll(item)
|
|
||||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
|
|
||||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
|
|
||||||
MusicMode.GENRES -> playbackModel.playFromGenre(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenMenu(item: Song, anchor: View) {
|
override fun onOpenMenu(item: Song, anchor: View) {
|
||||||
|
|
|
@ -65,7 +65,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
||||||
|
|
||||||
// Set up actions
|
// Set up actions
|
||||||
binding.playbackPlayPause.setOnClickListener { playbackModel.toggleIsPlaying() }
|
binding.playbackPlayPause.setOnClickListener { playbackModel.toggleIsPlaying() }
|
||||||
setupSecondaryActions(binding, PlaybackSettings.from(context).barAction)
|
setupSecondaryActions(binding, playbackModel.currentBarAction)
|
||||||
|
|
||||||
// Load the track color in manually as it's unclear whether the track actually supports
|
// Load the track color in manually as it's unclear whether the track actually supports
|
||||||
// using a ColorStateList in the resources.
|
// using a ColorStateList in the resources.
|
||||||
|
|
|
@ -36,7 +36,6 @@ import org.oxycblt.auxio.util.context
|
||||||
*/
|
*/
|
||||||
class PlaybackViewModel(application: Application) :
|
class PlaybackViewModel(application: Application) :
|
||||||
AndroidViewModel(application), PlaybackStateManager.Listener {
|
AndroidViewModel(application), PlaybackStateManager.Listener {
|
||||||
private val homeSettings = HomeSettings.from(application)
|
|
||||||
private val musicSettings = MusicSettings.from(application)
|
private val musicSettings = MusicSettings.from(application)
|
||||||
private val playbackSettings = PlaybackSettings.from(application)
|
private val playbackSettings = PlaybackSettings.from(application)
|
||||||
private val playbackManager = PlaybackStateManager.getInstance()
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
|
@ -84,6 +83,9 @@ class PlaybackViewModel(application: Application) :
|
||||||
val genrePickerSong: StateFlow<Song?>
|
val genrePickerSong: StateFlow<Song?>
|
||||||
get() = _genrePlaybackPickerSong
|
get() = _genrePlaybackPickerSong
|
||||||
|
|
||||||
|
/** The current action to show on the playback bar. */
|
||||||
|
val currentBarAction: ActionMode get() = playbackSettings.barAction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current audio session ID of the internal player. Null if no [InternalPlayer] is
|
* The current audio session ID of the internal player. Null if no [InternalPlayer] is
|
||||||
* available.
|
* available.
|
||||||
|
@ -143,6 +145,29 @@ class PlaybackViewModel(application: Application) :
|
||||||
|
|
||||||
// --- PLAYING FUNCTIONS ---
|
// --- PLAYING FUNCTIONS ---
|
||||||
|
|
||||||
|
/** Shuffle all songs in the music library. */
|
||||||
|
fun shuffleAll() {
|
||||||
|
playImpl(null, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a [Song] from the [MusicParent] outlined by the given [MusicMode].
|
||||||
|
* - If [MusicMode.SONGS], the [Song] is played from all songs.
|
||||||
|
* - If [MusicMode.ALBUMS], the [Song] is played from it's [Album].
|
||||||
|
* - If [MusicMode.ARTISTS], the [Song] is played from one of it's [Artist]s.
|
||||||
|
* - If [MusicMode.GENRES], the [Song] is played from one of it's [Genre]s.
|
||||||
|
* @param song The [Song] to play.
|
||||||
|
* @param playbackMode The [MusicMode] to play from.
|
||||||
|
*/
|
||||||
|
fun playFrom(song: Song, playbackMode: MusicMode) {
|
||||||
|
when (playbackMode) {
|
||||||
|
MusicMode.SONGS -> playFromAll(song)
|
||||||
|
MusicMode.ALBUMS -> playFromAlbum(song)
|
||||||
|
MusicMode.ARTISTS -> playFromArtist(song)
|
||||||
|
MusicMode.GENRES -> playFromGenre(song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the given [Song] from all songs in the music library.
|
* Play the given [Song] from all songs in the music library.
|
||||||
* @param song The [Song] to play.
|
* @param song The [Song] to play.
|
||||||
|
@ -151,11 +176,6 @@ class PlaybackViewModel(application: Application) :
|
||||||
playImpl(song, null)
|
playImpl(song, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shuffle all songs in the music library. */
|
|
||||||
fun shuffleAll() {
|
|
||||||
playImpl(null, null, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a [Song] from it's [Album].
|
* Play a [Song] from it's [Album].
|
||||||
* @param song The [Song] to play.
|
* @param song The [Song] to play.
|
||||||
|
@ -203,6 +223,15 @@ class PlaybackViewModel(application: Application) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the [Genre] playback choice process as complete. This should occur when the [Genre]
|
||||||
|
* choice dialog is opened after this flag is detected.
|
||||||
|
* @see playFromGenre
|
||||||
|
*/
|
||||||
|
fun finishPlaybackGenrePicker() {
|
||||||
|
_genrePlaybackPickerSong.value = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play an [Album].
|
* Play an [Album].
|
||||||
* @param album The [Album] to play.
|
* @param album The [Album] to play.
|
||||||
|
|
|
@ -136,14 +136,8 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
|
|
||||||
override fun onRealClick(item: Music) {
|
override fun onRealClick(item: Music) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is Song ->
|
|
||||||
when (PlaybackSettings.from(requireContext()).inListPlaybackMode) {
|
|
||||||
MusicMode.SONGS -> playbackModel.playFromAll(item)
|
|
||||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
|
|
||||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
|
|
||||||
MusicMode.GENRES -> playbackModel.playFromGenre(item)
|
|
||||||
}
|
|
||||||
is MusicParent -> navModel.exploreNavigateTo(item)
|
is MusicParent -> navModel.exploreNavigateTo(item)
|
||||||
|
is Song -> playbackModel.playFrom(item, searchModel.playbackMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.oxycblt.auxio.music.*
|
||||||
import org.oxycblt.auxio.music.Library
|
import org.oxycblt.auxio.music.Library
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Sort
|
import org.oxycblt.auxio.music.Sort
|
||||||
|
import org.oxycblt.auxio.playback.PlaybackSettings
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
|
@ -44,7 +45,8 @@ import org.oxycblt.auxio.util.logD
|
||||||
class SearchViewModel(application: Application) :
|
class SearchViewModel(application: Application) :
|
||||||
AndroidViewModel(application), MusicStore.Listener {
|
AndroidViewModel(application), MusicStore.Listener {
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
private val settings = SearchSettings.from(application)
|
private val searchSettings = SearchSettings.from(application)
|
||||||
|
private val playbackSettings = PlaybackSettings.from(application)
|
||||||
private var lastQuery: String? = null
|
private var lastQuery: String? = null
|
||||||
private var currentSearchJob: Job? = null
|
private var currentSearchJob: Job? = null
|
||||||
|
|
||||||
|
@ -53,6 +55,9 @@ class SearchViewModel(application: Application) :
|
||||||
val searchResults: StateFlow<List<Item>>
|
val searchResults: StateFlow<List<Item>>
|
||||||
get() = _searchResults
|
get() = _searchResults
|
||||||
|
|
||||||
|
/** The [MusicMode] to use when playing a [Song] from the UI. */
|
||||||
|
val playbackMode: MusicMode get() = playbackSettings.inListPlaybackMode
|
||||||
|
|
||||||
init {
|
init {
|
||||||
musicStore.addListener(this)
|
musicStore.addListener(this)
|
||||||
}
|
}
|
||||||
|
@ -97,7 +102,7 @@ class SearchViewModel(application: Application) :
|
||||||
|
|
||||||
private fun searchImpl(library: Library, query: String): List<Item> {
|
private fun searchImpl(library: Library, query: String): List<Item> {
|
||||||
val sort = Sort(Sort.Mode.ByName, true)
|
val sort = Sort(Sort.Mode.ByName, true)
|
||||||
val filterMode = settings.searchFilterMode
|
val filterMode = searchSettings.searchFilterMode
|
||||||
val results = mutableListOf<Item>()
|
val results = mutableListOf<Item>()
|
||||||
|
|
||||||
// Note: A null filter mode maps to the "All" filter option, hence the check.
|
// Note: A null filter mode maps to the "All" filter option, hence the check.
|
||||||
|
@ -182,7 +187,7 @@ class SearchViewModel(application: Application) :
|
||||||
*/
|
*/
|
||||||
@IdRes
|
@IdRes
|
||||||
fun getFilterOptionId() =
|
fun getFilterOptionId() =
|
||||||
when (settings.searchFilterMode) {
|
when (searchSettings.searchFilterMode) {
|
||||||
MusicMode.SONGS -> R.id.option_filter_songs
|
MusicMode.SONGS -> R.id.option_filter_songs
|
||||||
MusicMode.ALBUMS -> R.id.option_filter_albums
|
MusicMode.ALBUMS -> R.id.option_filter_albums
|
||||||
MusicMode.ARTISTS -> R.id.option_filter_artists
|
MusicMode.ARTISTS -> R.id.option_filter_artists
|
||||||
|
@ -207,7 +212,7 @@ class SearchViewModel(application: Application) :
|
||||||
else -> error("Invalid option ID provided")
|
else -> error("Invalid option ID provided")
|
||||||
}
|
}
|
||||||
logD("Updating filter mode to $newFilterMode")
|
logD("Updating filter mode to $newFilterMode")
|
||||||
settings.searchFilterMode = newFilterMode
|
searchSettings.searchFilterMode = newFilterMode
|
||||||
search(lastQuery)
|
search(lastQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue