diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 15f772121..01638465e 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 5b09909cf..da12a9212 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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 --- diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 93025cc97..4830b49b6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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 }!! ) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 1904b9c28..587af7192 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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 }!! ) diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt index 06b0f8260..b7cbba266 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index 59a981692..2c1b8fb1f 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -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() 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)) diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt index 7dcf8117a..0197138f6 100644 --- a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt new file mode 100644 index 000000000..41d111758 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt @@ -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() + val response: LiveData get() = mResponse + + private val mRedo = MutableLiveData() + val doReload: LiveData get() = mRedo + + private val mDoGrant = MutableLiveData() + val doGrant: LiveData 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 create(modelClass: Class): T { + if (modelClass.isAssignableFrom(LoadingViewModel::class.java)) { + return LoadingViewModel(application) as T + } + + throw IllegalArgumentException("Unknown ViewModel class.") + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt new file mode 100644 index 000000000..a48d358ff --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -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() + val genres: List get() = mGenres + + private var mArtists = listOf() + val artists: List get() = mArtists + + private var mAlbums = listOf() + val albums: List get() = mAlbums + + private var mSongs = listOf() + val songs: List 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 { + 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 + } + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt deleted file mode 100644 index 052df6c56..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ /dev/null @@ -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>() - val genres: LiveData> get() = mGenres - - private val mArtists = MutableLiveData>() - val artists: LiveData> get() = mArtists - - private val mAlbums = MutableLiveData>() - val albums: LiveData> get() = mAlbums - - private val mSongs = MutableLiveData>() - val songs: LiveData> get() = mSongs - - private val mResponse = MutableLiveData() - val response: LiveData get() = mResponse - - // UI control - private val mRedo = MutableLiveData() - val doReload: LiveData get() = mRedo - - private val mDoGrant = MutableLiveData() - val doGrant: LiveData 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 create(modelClass: Class): T { - if (modelClass.isAssignableFrom(MusicViewModel::class.java)) { - return MusicViewModel(application) as T - } - - throw IllegalArgumentException("Unknown ViewModel class.") - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt index a81bf2f1e..628444d4c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index ff8d9f98b..44f3269b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -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()) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 9f583e09e..30649f37b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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) { + 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index 598aa1b75..bc05dd17e 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -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) diff --git a/app/src/main/res/layout/fragment_loading.xml b/app/src/main/res/layout/fragment_loading.xml index 2db6c65d7..eacf7b7f6 100644 --- a/app/src/main/res/layout/fragment_loading.xml +++ b/app/src/main/res/layout/fragment_loading.xml @@ -7,8 +7,8 @@ + name="loadingModel" + type="org.oxycblt.auxio.loading.LoadingViewModel" />