From fe0c2761c7c71a4d64f26debb318fc6977b8d67e Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 24 Oct 2021 20:01:15 -0600 Subject: [PATCH] music: make musicloader async Make MusicLoader instantiation fully asynchronous. This implementation changes a lot about Auxio. For one, the loading screen is now gone. However, many parts of the app now run under the fact that MusicStore might not be available. However, I don't think there will be too much bugs from it. Some more changes will be made to improve this implementation. --- .../java/org/oxycblt/auxio/MainFragment.kt | 2 - .../oxycblt/auxio/detail/DetailViewModel.kt | 7 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 95 ++++++++- .../org/oxycblt/auxio/home/HomeViewModel.kt | 63 ++++-- .../home/fastscroll/FastScrollRecyclerView.kt | 17 +- .../home/fastscroll/Md2PopupBackground.kt | 3 +- .../auxio/home/list/SongListFragment.kt | 2 - .../oxycblt/auxio/loading/LoadingFragment.kt | 197 ------------------ .../oxycblt/auxio/loading/LoadingViewModel.kt | 82 -------- .../org/oxycblt/auxio/music/MusicStore.kt | 102 ++++++--- .../auxio/playback/PlaybackViewModel.kt | 15 +- .../playback/state/PlaybackStateManager.kt | 80 +++---- .../auxio/playback/system/AudioReactor.kt | 2 +- .../auxio/playback/system/PlaybackService.kt | 2 +- .../system/PlaybackSessionConnector.kt | 2 +- .../oxycblt/auxio/search/SearchViewModel.kt | 4 +- .../oxycblt/auxio/settings/AboutFragment.kt | 19 +- .../java/org/oxycblt/auxio/util/ViewUtil.kt | 10 +- .../oxycblt/auxio/widgets/WidgetController.kt | 2 +- app/src/main/res/layout/fragment_home.xml | 1 + app/src/main/res/layout/fragment_loading.xml | 64 ------ app/src/main/res/navigation/nav_main.xml | 18 +- 22 files changed, 286 insertions(+), 503 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt delete mode 100644 app/src/main/res/layout/fragment_loading.xml diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 0c97359d5..9500b24c7 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -58,8 +58,6 @@ class MainFragment : Fragment() { // --- VIEWMODEL SETUP --- - playbackModel.setupPlayback(requireContext()) - // Change CompactPlaybackFragment's visibility here so that an animation occurs. binding.mainPlayback.isVisible = playbackModel.song.value != null diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 13b05e040..5a98d59a2 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -78,23 +78,28 @@ class DetailViewModel : ViewModel() { private var currentMenuContext: DisplayMode? = null - private val musicStore = MusicStore.getInstance() private val settingsManager = SettingsManager.getInstance() fun setGenre(id: Long, context: Context) { if (mCurGenre.value?.id == id) return + + val musicStore = MusicStore.requireInstance() mCurGenre.value = musicStore.genres.find { it.id == id } refreshGenreData(context) } fun setArtist(id: Long, context: Context) { if (mCurArtist.value?.id == id) return + + val musicStore = MusicStore.requireInstance() mCurArtist.value = musicStore.artists.find { it.id == id } refreshArtistData(context) } fun setAlbum(id: Long, context: Context) { if (mCurAlbum.value?.id == id) return + + val musicStore = MusicStore.requireInstance() mCurAlbum.value = musicStore.albums.find { it.id == id } refreshAlbumData(context) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 0cf230e62..82248458c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -18,12 +18,16 @@ package org.oxycblt.auxio.home +import android.Manifest import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.Button +import androidx.activity.result.contract.ActivityResultContracts import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.content.res.ResourcesCompat import androidx.core.view.iterator import androidx.core.view.updatePadding import androidx.core.view.updatePaddingRelative @@ -34,6 +38,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R @@ -46,6 +51,7 @@ import org.oxycblt.auxio.home.list.SongListFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode @@ -73,6 +79,13 @@ class HomeFragment : Fragment() { var bottomPadding = 0 val sortItem: MenuItem + // Build the permission launcher here as you can only do it in onCreateView/onCreate + val permLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { + homeModel.reloadMusic(requireContext()) + } + // --- UI SETUP --- binding.lifecycleOwner = viewLifecycleOwner @@ -193,22 +206,96 @@ class HomeFragment : Fragment() { registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position) }) + + TabLayoutMediator(binding.homeTabs, this) { tab, pos -> + tab.setText(homeModel.tabs[pos].string) + }.attach() } binding.homeFab.setOnClickListener { playbackModel.shuffleAll() } - TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos -> - tab.setText(homeModel.tabs[pos].string) - }.attach() - // --- VIEWMODEL SETUP --- + // Initialize music loading. Unlike MainFragment, we can not only do this here on startup + // but also show a SnackBar in a reasonable place in this fragment. + homeModel.loadMusic(requireContext()) + // There is no way a fast scrolling event can continue across a re-create. Reset it. homeModel.updateFastScrolling(false) + homeModel.loaderResponse.observe(viewLifecycleOwner) { response -> + // Handle the loader response. + when (response) { + is MusicStore.Response.Ok -> { + logD("Received Ok") + + binding.homeFab.show() + playbackModel.setupPlayback(requireContext()) + } + + is MusicStore.Response.Err -> { + logD("Received Error") + + // We received an error. Hide the FAB and show a Snackbar with the error + // message and a corresponding action + binding.homeFab.hide() + + val errorRes = when (response.kind) { + MusicStore.ErrorKind.NO_MUSIC -> R.string.err_no_music + MusicStore.ErrorKind.NO_PERMS -> R.string.err_no_perms + MusicStore.ErrorKind.FAILED -> R.string.err_load_failed + } + + val snackbar = Snackbar.make( + binding.root, getString(errorRes), Snackbar.LENGTH_INDEFINITE + ) + + snackbar.view.apply { + // Change the font family to our semibold color + findViewById