Change music storage to shared object

Change MusicViewModel from a ViewModel to a shared object, so that it can be used everywhere in the app instead of just in fragments.
This commit is contained in:
OxygenCobalt 2020-10-13 16:44:14 -06:00
parent 3376b57f8e
commit 96c30b3f93
15 changed files with 253 additions and 233 deletions

View file

@ -14,7 +14,8 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.library.LibraryFragment
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.loading.LoadingViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.songs.SongsFragment
import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.getInactiveAlpha
@ -22,8 +23,8 @@ import org.oxycblt.auxio.theme.getTransparentAccent
import org.oxycblt.auxio.theme.toColor
class MainFragment : Fragment() {
private val musicModel: MusicViewModel by activityViewModels {
MusicViewModel.Factory(requireActivity().application)
private val loadingModel: LoadingViewModel by activityViewModels {
LoadingViewModel.Factory(requireActivity().application)
}
private val shownFragments = listOf(0, 1)
@ -39,9 +40,9 @@ class MainFragment : Fragment() {
): View? {
val binding = FragmentMainBinding.inflate(inflater)
// If musicModel was cleared while the app was closed [Likely due to Auxio being suspended
// If the music was cleared while the app was closed [Likely due to Auxio being suspended
// in the background], then navigate back to LoadingFragment to reload the music.
if (musicModel.response.value == null) {
if (MusicStore.getInstance().songs.isEmpty()) {
findNavController().navigate(MainFragmentDirections.actionReturnToLoading())
return null

View file

@ -12,7 +12,7 @@ import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailSongAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.disable
@ -22,7 +22,6 @@ class AlbumDetailFragment : Fragment() {
private val args: AlbumDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
@ -31,22 +30,21 @@ class AlbumDetailFragment : Fragment() {
): View? {
val binding = FragmentAlbumDetailBinding.inflate(inflater)
// If DetailViewModel isn't already storing the album, get it from MusicViewModel
// If DetailViewModel isn't already storing the album, get it from MusicStore
// using the ID given by the navigation arguments.
if (detailModel.currentAlbum.value == null ||
detailModel.currentAlbum.value?.id != args.albumId
) {
val musicModel: MusicViewModel by activityViewModels()
detailModel.updateAlbum(
musicModel.albums.value!!.find {
MusicStore.getInstance().albums.find {
it.id == args.albumId
}!!
)
}
val songAdapter = DetailSongAdapter {
playbackModel.update(it, musicModel.songs.value!!)
playbackModel.update(it)
}
// --- UI SETUP ---

View file

@ -11,7 +11,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.disable
@ -30,15 +30,13 @@ class ArtistDetailFragment : Fragment() {
): View? {
val binding = FragmentArtistDetailBinding.inflate(inflater)
// If DetailViewModel isn't already storing the artist, get it from MusicViewModel
// If DetailViewModel isn't already storing the artist, get it from MusicStore
// using the ID given by the navigation arguments
if (detailModel.currentArtist.value == null ||
detailModel.currentArtist.value?.id != args.artistId
) {
val musicModel: MusicViewModel by activityViewModels()
detailModel.updateArtist(
musicModel.artists.value!!.find {
MusicStore.getInstance().artists.find {
it.id == args.artistId
}!!
)

View file

@ -11,7 +11,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.databinding.FragmentGenreDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailArtistAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.disable
@ -27,15 +27,13 @@ class GenreDetailFragment : Fragment() {
): View? {
val binding = FragmentGenreDetailBinding.inflate(inflater)
// If DetailViewModel isn't already storing the genre, get it from MusicViewModel
// If DetailViewModel isn't already storing the genre, get it from MusicStore
// using the ID given by the navigation arguments
if (detailModel.currentGenre.value == null ||
detailModel.currentGenre.value?.id != args.genreId
) {
val musicModel: MusicViewModel by activityViewModels()
detailModel.updateGenre(
musicModel.genres.value!!.find {
MusicStore.getInstance().genres.find {
it.id == args.genreId
}!!
)

View file

@ -23,10 +23,9 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.ShowMode
import org.oxycblt.auxio.theme.applyColor
import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.resolveAttr
@ -34,10 +33,6 @@ import org.oxycblt.auxio.theme.resolveAttr
// A Fragment to show all the music in the Library.
class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
private val musicModel: MusicViewModel by activityViewModels {
MusicViewModel.Factory(requireActivity().application)
}
private val libraryModel: LibraryViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels()
@ -48,6 +43,8 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
): View? {
val binding = FragmentLibraryBinding.inflate(inflater)
val musicStore = MusicStore.getInstance()
val libraryAdapter = LibraryAdapter(libraryModel.showMode.value!!) {
navToItem(it)
}
@ -137,13 +134,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
// Update the adapter with the new data
libraryAdapter.updateData(
mode.getSortedBaseModelList(
when (libraryModel.showMode.value) {
ShowMode.SHOW_GENRES -> musicModel.genres.value!!
ShowMode.SHOW_ARTISTS -> musicModel.artists.value!!
ShowMode.SHOW_ALBUMS -> musicModel.albums.value!!
else -> musicModel.artists.value!!
}
musicStore.getListForShowMode(libraryModel.showMode.value!!)
)
)
@ -179,7 +170,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean = false
override fun onQueryTextChange(query: String): Boolean {
libraryModel.updateSearchQuery(query, musicModel)
libraryModel.updateSearchQuery(query)
return false
}
@ -190,7 +181,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
// If the item is a song [That was selected through search], then update the playback
// to that song instead of doing any navigation
if (baseModel is Song) {
playbackModel.update(baseModel, musicModel.songs.value!!)
playbackModel.update(baseModel)
return
}

View file

@ -9,7 +9,7 @@ import kotlinx.coroutines.launch
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.recycler.ShowMode
import org.oxycblt.auxio.recycler.SortMode
@ -44,7 +44,7 @@ class LibraryViewModel : ViewModel() {
}
}
fun updateSearchQuery(query: String, musicModel: MusicViewModel) {
fun updateSearchQuery(query: String) {
// Don't bother if the query is blank.
if (query == "") {
resetQuery()
@ -52,16 +52,17 @@ class LibraryViewModel : ViewModel() {
return
}
// Search MusicViewModel for all the items [Artists, Albums, Songs] that contain
// Search MusicStore for all the items [Artists, Albums, Songs] that contain
// the query, and update the LiveData with those items. This is done on a separate
// thread as it can be a very long operation for large music libraries.
viewModelScope.launch {
val musicStore = MusicStore.getInstance()
val combined = mutableListOf<BaseModel>()
val children = showMode.value!!.getChildren()
// If the Library ShowMode supports it, include artists / genres in the search.
if (children.contains(ShowMode.SHOW_GENRES)) {
val genres = musicModel.genres.value!!.filter { it.name.contains(query, true) }
val genres = musicStore.genres.filter { it.name.contains(query, true) }
if (genres.isNotEmpty()) {
combined.add(Header(id = ShowMode.SHOW_GENRES.constant))
@ -70,7 +71,7 @@ class LibraryViewModel : ViewModel() {
}
if (children.contains(ShowMode.SHOW_ARTISTS)) {
val artists = musicModel.artists.value!!.filter { it.name.contains(query, true) }
val artists = musicStore.artists.filter { it.name.contains(query, true) }
if (artists.isNotEmpty()) {
combined.add(Header(id = ShowMode.SHOW_ARTISTS.constant))
@ -79,14 +80,14 @@ class LibraryViewModel : ViewModel() {
}
// Albums & Songs are always included.
val albums = musicModel.albums.value!!.filter { it.name.contains(query, true) }
val albums = musicStore.albums.filter { it.name.contains(query, true) }
if (albums.isNotEmpty()) {
combined.add(Header(id = ShowMode.SHOW_ALBUMS.constant))
combined.addAll(albums)
}
val songs = musicModel.songs.value!!.filter { it.name.contains(query, true) }
val songs = musicStore.songs.filter { it.name.contains(query, true) }
if (songs.isNotEmpty()) {
combined.add(Header(id = ShowMode.SHOW_SONGS.constant))

View file

@ -14,13 +14,12 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
class LoadingFragment : Fragment(R.layout.fragment_loading) {
private val musicModel: MusicViewModel by activityViewModels {
MusicViewModel.Factory(requireActivity().application)
private val loadingModel: LoadingViewModel by activityViewModels {
LoadingViewModel.Factory(requireActivity().application)
}
override fun onCreateView(
@ -39,18 +38,18 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
if (granted) {
returnToLoading(binding)
musicModel.reload()
loadingModel.reload()
}
}
// --- UI SETUP ---
binding.lifecycleOwner = this
binding.musicModel = musicModel
binding.loadingModel = loadingModel
// --- VIEWMODEL SETUP ---
musicModel.response.observe(viewLifecycleOwner) {
loadingModel.response.observe(viewLifecycleOwner) {
if (it == MusicLoaderResponse.DONE) {
findNavController().navigate(
LoadingFragmentDirections.actionToMain()
@ -69,17 +68,17 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
}
}
musicModel.doReload.observe(viewLifecycleOwner) {
loadingModel.doReload.observe(viewLifecycleOwner) {
if (it) {
returnToLoading(binding)
musicModel.doneWithReload()
loadingModel.doneWithReload()
}
}
musicModel.doGrant.observe(viewLifecycleOwner) {
loadingModel.doGrant.observe(viewLifecycleOwner) {
if (it) {
permLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
musicModel.doneWithGrant()
loadingModel.doneWithGrant()
}
}
@ -90,7 +89,7 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
binding.loadingGrantButton.visibility = View.VISIBLE
binding.loadingErrorText.text = getString(R.string.error_no_perms)
} else {
musicModel.go()
loadingModel.go()
}
Log.d(this::class.simpleName, "Fragment created.")
@ -109,7 +108,7 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
) == PackageManager.PERMISSION_DENIED
}
// Remove the loading ui_indicator and show the error groups
// Remove the loading indicator and show the error groups
private fun showError(binding: FragmentLoadingBinding) {
binding.loadingBar.visibility = View.GONE
binding.loadingErrorIcon.visibility = View.VISIBLE

View file

@ -0,0 +1,95 @@
package org.oxycblt.auxio.loading
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
class LoadingViewModel(private val app: Application) : ViewModel() {
// Coroutine
private val loadingJob = Job()
private val ioScope = CoroutineScope(
loadingJob + Dispatchers.IO
)
// UI control
private val mResponse = MutableLiveData<MusicLoaderResponse>()
val response: LiveData<MusicLoaderResponse> get() = mResponse
private val mRedo = MutableLiveData<Boolean>()
val doReload: LiveData<Boolean> get() = mRedo
private val mDoGrant = MutableLiveData<Boolean>()
val doGrant: LiveData<Boolean> get() = mDoGrant
private var started = false
// Start the music loading sequence.
// This should only be ran once, use reload() for all other loads.
fun go() {
if (!started) {
started = true
doLoad()
}
}
private fun doLoad() {
ioScope.launch {
val musicStore = MusicStore.getInstance()
val response = musicStore.load(app)
withContext(Dispatchers.Main) {
mResponse.value = response
}
}
}
// UI communication functions
// LoadingFragment uses these so that button presses can update the ViewModel.
// all doneWithX functions are to reset the value so that LoadingFragment doesn't
// repeat commands if the view is recreated.
fun reload() {
mRedo.value = true
doLoad()
}
fun doneWithReload() {
mRedo.value = false
}
fun grant() {
mDoGrant.value = true
}
fun doneWithGrant() {
mDoGrant.value = false
}
override fun onCleared() {
super.onCleared()
// Cancel the current loading job if the app has been stopped
loadingJob.cancel()
}
class Factory(private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(LoadingViewModel::class.java)) {
return LoadingViewModel(application) as T
}
throw IllegalArgumentException("Unknown ViewModel class.")
}
}
}

View file

@ -0,0 +1,100 @@
package org.oxycblt.auxio.music
import android.app.Application
import android.util.Log
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.processing.MusicLoader
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
import org.oxycblt.auxio.music.processing.MusicSorter
import org.oxycblt.auxio.recycler.ShowMode
// Storage for Music Data. Only use getInstance() to access this object.
class MusicStore private constructor() {
private var mGenres = listOf<Genre>()
val genres: List<Genre> get() = mGenres
private var mArtists = listOf<Artist>()
val artists: List<Artist> get() = mArtists
private var mAlbums = listOf<Album>()
val albums: List<Album> get() = mAlbums
private var mSongs = listOf<Song>()
val songs: List<Song> get() = mSongs
suspend fun load(app: Application): MusicLoaderResponse {
Log.i(this::class.simpleName, "Starting initial music load...")
val start = System.currentTimeMillis()
// Get the placeholder strings, which are used by MusicLoader & MusicSorter for
// any music that doesn't have metadata.
val genrePlaceholder = app.getString(R.string.placeholder_genre)
val artistPlaceholder = app.getString(R.string.placeholder_artist)
val albumPlaceholder = app.getString(R.string.placeholder_album)
val loader = MusicLoader(
app.contentResolver,
genrePlaceholder,
artistPlaceholder,
albumPlaceholder
)
if (loader.response == MusicLoaderResponse.DONE) {
// If the loading succeeds, then sort the songs and update the value
val sorter = MusicSorter(
loader.genres,
loader.artists,
loader.albums,
loader.songs,
genrePlaceholder,
artistPlaceholder,
albumPlaceholder
)
mSongs = sorter.songs.toList()
mAlbums = sorter.albums.toList()
mArtists = sorter.artists.toList()
mGenres = sorter.genres.toList()
val elapsed = System.currentTimeMillis() - start
Log.i(
this::class.simpleName,
"Music load completed successfully in ${elapsed}ms."
)
}
return loader.response
}
fun getListForShowMode(showMode: ShowMode): List<BaseModel> {
return when (showMode) {
ShowMode.SHOW_GENRES -> mGenres
ShowMode.SHOW_ARTISTS -> mArtists
ShowMode.SHOW_ALBUMS -> mAlbums
ShowMode.SHOW_SONGS -> mSongs
}
}
companion object {
@Volatile
private var INSTANCE: MusicStore? = null
fun getInstance(): MusicStore {
val currentInstance = INSTANCE
if (currentInstance != null) {
return currentInstance
}
synchronized(this) {
val newInstance = MusicStore()
INSTANCE = newInstance
return newInstance
}
}
}
}

View file

@ -1,154 +0,0 @@
package org.oxycblt.auxio.music
import android.app.Application
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.processing.MusicLoader
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
import org.oxycblt.auxio.music.processing.MusicSorter
// ViewModel for music storage.
// TODO: Move genre usage to songs [If there's a way to find songs without a genre]
class MusicViewModel(private val app: Application) : ViewModel() {
// Coroutine
private val loadingJob = Job()
private val ioScope = CoroutineScope(
loadingJob + Dispatchers.IO
)
// Values
private val mGenres = MutableLiveData<List<Genre>>()
val genres: LiveData<List<Genre>> get() = mGenres
private val mArtists = MutableLiveData<List<Artist>>()
val artists: LiveData<List<Artist>> get() = mArtists
private val mAlbums = MutableLiveData<List<Album>>()
val albums: LiveData<List<Album>> get() = mAlbums
private val mSongs = MutableLiveData<List<Song>>()
val songs: LiveData<List<Song>> get() = mSongs
private val mResponse = MutableLiveData<MusicLoaderResponse>()
val response: LiveData<MusicLoaderResponse> get() = mResponse
// UI control
private val mRedo = MutableLiveData<Boolean>()
val doReload: LiveData<Boolean> get() = mRedo
private val mDoGrant = MutableLiveData<Boolean>()
val doGrant: LiveData<Boolean> get() = mDoGrant
private var started = false
// Start the music loading sequence.
// This should only be ran once, use reload() for all other loads.
fun go() {
if (!started) {
started = true
doLoad()
}
}
private fun doLoad() {
Log.i(this::class.simpleName, "Starting initial music load...")
ioScope.launch {
val start = System.currentTimeMillis()
// Get the placeholder strings, which are used by MusicLoader & MusicSorter for
// any music that doesn't have metadata.
val genrePlaceholder = app.getString(R.string.placeholder_genre)
val artistPlaceholder = app.getString(R.string.placeholder_artist)
val albumPlaceholder = app.getString(R.string.placeholder_album)
val loader = MusicLoader(
app.contentResolver,
genrePlaceholder,
artistPlaceholder,
albumPlaceholder
)
withContext(Dispatchers.Main) {
if (loader.response == MusicLoaderResponse.DONE) {
// If the loading succeeds, then sort the songs and update the value
val sorter = MusicSorter(
loader.genres,
loader.artists,
loader.albums,
loader.songs,
genrePlaceholder,
artistPlaceholder,
albumPlaceholder
)
mSongs.value = sorter.songs.toList()
mAlbums.value = sorter.albums.toList()
mArtists.value = sorter.artists.toList()
mGenres.value = sorter.genres.toList()
}
mResponse.value = loader.response
val elapsed = System.currentTimeMillis() - start
Log.i(
this::class.simpleName,
"Music load completed successfully in ${elapsed}ms."
)
}
}
}
// UI communication functions
// LoadingFragment uses these so that button presses can update the ViewModel.
// all doneWithX functions are to reset the value so that LoadingFragment doesn't
// repeat commands if the view is recreated.
fun reload() {
mRedo.value = true
doLoad()
}
fun doneWithReload() {
mRedo.value = false
}
fun grant() {
mDoGrant.value = true
}
fun doneWithGrant() {
mDoGrant.value = false
}
override fun onCleared() {
super.onCleared()
// Cancel the current loading job if the app has been stopped
loadingJob.cancel()
}
class Factory(private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MusicViewModel::class.java)) {
return MusicViewModel(application) as T
}
throw IllegalArgumentException("Unknown ViewModel class.")
}
}
}

View file

@ -13,14 +13,9 @@ import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
import org.oxycblt.auxio.music.MusicViewModel
import kotlin.time.seconds
import org.oxycblt.auxio.music.MusicStore
class CompactPlaybackFragment : Fragment() {
private val musicModel: MusicViewModel by activityViewModels {
MusicViewModel.Factory(requireActivity().application)
}
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView(
@ -44,7 +39,7 @@ class CompactPlaybackFragment : Fragment() {
// Put a placeholder song in the binding & hide the playback fragment initially,
// as for some reason the attach event doesn't register anymore w/LiveData
binding.song = musicModel.songs.value!![0]
binding.song = MusicStore.getInstance().songs[0]
binding.playbackModel = playbackModel
binding.root.visibility = View.GONE

View file

@ -78,12 +78,9 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
binding.playbackSkipPrev.disable(requireContext())
}
Log.d(this::class.simpleName, it.toString())
if (it < playbackModel.queue.value!!.lastIndex) {
binding.playbackSkipNext.enable(requireContext())
} else {
Log.d(this::class.simpleName, "Fucking stupid retard.")
binding.playbackSkipNext.disable(requireContext())
}
}

View file

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toDuration
@ -40,11 +41,13 @@ class PlaybackViewModel : ViewModel() {
}
// Update the current song while changing the queue to All Songs.
fun update(song: Song, allSongs: List<Song>) {
fun update(song: Song) {
val musicStore = MusicStore.getInstance()
updatePlayback(song)
mQueue.value = allSongs.toMutableList()
mCurrentIndex.value = allSongs.indexOf(song)
mQueue.value = musicStore.songs.toMutableList()
mCurrentIndex.value = musicStore.songs.indexOf(song)
}
private fun updatePlayback(song: Song) {

View file

@ -8,15 +8,11 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.databinding.FragmentSongsBinding
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.theme.applyDivider
class SongsFragment : Fragment() {
private val musicModel: MusicViewModel by activityViewModels {
MusicViewModel.Factory(requireActivity().application)
}
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView(
@ -26,14 +22,16 @@ class SongsFragment : Fragment() {
): View? {
val binding = FragmentSongsBinding.inflate(inflater)
val musicStore = MusicStore.getInstance()
// TODO: Add option to search songs if LibraryFragment isn't enabled
// TODO: Maybe add fast scrolling or sorting
// --- UI SETUP ---
binding.songRecycler.apply {
adapter = SongAdapter(musicModel.songs.value!!) {
playbackModel.update(it, musicModel.songs.value!!)
adapter = SongAdapter(musicStore.songs) {
playbackModel.update(it)
}
applyDivider()
setHasFixedSize(true)

View file

@ -7,8 +7,8 @@
<data>
<variable
name="musicModel"
type="org.oxycblt.auxio.music.MusicViewModel" />
name="loadingModel"
type="org.oxycblt.auxio.loading.LoadingViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -66,7 +66,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_semibold"
android:onClick="@{() -> musicModel.reload()}"
android:onClick="@{() -> loadingModel.reload()}"
android:text="@string/label_retry"
android:textColor="?attr/colorPrimary"
android:visibility="gone"
@ -85,7 +85,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_semibold"
android:onClick="@{() -> musicModel.grant()}"
android:onClick="@{() -> loadingModel.grant()}"
android:text="@string/label_grant"
android:textColor="?attr/colorPrimary"
android:visibility="gone"