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:
Alexander Capehart 2023-01-06 19:58:52 -07:00
parent ac9f50c0a0
commit 6fa53ab873
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
16 changed files with 96 additions and 69 deletions

View file

@ -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

View file

@ -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()
} }
} }

View file

@ -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)
} }

View file

@ -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}")
} }
} }

View file

@ -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)
} }

View file

@ -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}")
} }
} }

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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])

View file

@ -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) {

View file

@ -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.

View file

@ -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.

View file

@ -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)
} }
} }

View file

@ -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)
} }