From 4db4b023d57b050678379cee7d8651fdd2f379d4 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sun, 1 Jan 2023 17:01:22 -0700 Subject: [PATCH] home: temporarily re-add no music case I actually have no idea how to go about decoupling the other music loading errors from the "No Music" case without a very jarring user experience, so I'm not doing it for now. --- .../org/oxycblt/auxio/home/HomeFragment.kt | 60 ++++++++++++------- .../org/oxycblt/auxio/music/system/Indexer.kt | 15 ++++- .../auxio/ui/CoordinatorAppBarLayout.kt | 12 ++-- 3 files changed, 55 insertions(+), 32 deletions(-) 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 201fde2a8..36c138200 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -338,28 +338,43 @@ class HomeFragment : val throwable = unlikelyToBeNull(result.exceptionOrNull()) binding.homeIndexingContainer.visibility = View.VISIBLE binding.homeIndexingProgress.visibility = View.INVISIBLE - if (throwable is Indexer.NoPermissionException) { - logD("Updating UI to permission request state") - binding.homeIndexingStatus.text = context.getString(R.string.err_no_perms) - // Configure the action to act as a permission launcher. - binding.homeIndexingAction.apply { - visibility = View.VISIBLE - text = context.getString(R.string.lbl_grant) - setOnClickListener { - requireNotNull(storagePermissionLauncher) { - "Permission launcher was not available" - } - .launch(Indexer.PERMISSION_READ_AUDIO) + when (throwable) { + is Indexer.NoPermissionException -> { + logD("Updating UI to permission request state") + binding.homeIndexingStatus.text = context.getString(R.string.err_no_perms) + // Configure the action to act as a permission launcher. + binding.homeIndexingAction.apply { + visibility = View.VISIBLE + text = context.getString(R.string.lbl_grant) + setOnClickListener { + requireNotNull(storagePermissionLauncher) { + "Permission launcher was not available" + } + .launch(Indexer.PERMISSION_READ_AUDIO) + } } } - } else { - logD("Updating UI to error state") - binding.homeIndexingStatus.text = context.getString(R.string.err_index_failed) - // Configure the action to act as a reload trigger. - binding.homeIndexingAction.apply { - visibility = View.VISIBLE - text = context.getString(R.string.lbl_retry) - setOnClickListener { musicModel.refresh() } + is Indexer.NoMusicException -> { + logD("Updating UI to no music state") + // TODO: Rework how empty libraries are treated to feel less jarring if + // there was a previously loaded library + binding.homeIndexingStatus.text = context.getString(R.string.err_no_music) + // Configure the action to act as a reload trigger. + binding.homeIndexingAction.apply { + visibility = View.VISIBLE + text = context.getString(R.string.lbl_retry) + setOnClickListener { musicModel.refresh() } + } + } + else -> { + logD("Updating UI to error state") + binding.homeIndexingStatus.text = context.getString(R.string.err_index_failed) + // Configure the action to act as a reload trigger. + binding.homeIndexingAction.apply { + visibility = View.VISIBLE + text = context.getString(R.string.lbl_retry) + setOnClickListener { musicModel.rescan() } + } } } } @@ -420,10 +435,9 @@ class HomeFragment : val binding = requireBinding() if (binding.homeSelectionToolbar.updateSelectionAmount(selected.size) && selected.isNotEmpty()) { + // New selection started, show the AppBarLayout to indicate the new state. logD("Significant selection occurred, expanding AppBar") - // Significant enough change where we want to expand the RecyclerView - binding.homeAppbar.expandWithRecycler( - binding.homePager.findViewById(getTabRecyclerId(homeModel.currentTabMode.value))) + binding.homeAppbar.expandWithScrollingRecycler() } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt index 6a8d24456..ad1bbc01d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt @@ -197,7 +197,9 @@ class Indexer private constructor() { * @param context [Context] required to load music. * @param withCache Whether to use the cache or not when loading. If false, the cache will still * be written, but no cache entries will be loaded into the new library. - * @return A newly-loaded [MusicStore.Library]. May be empty. + * @return A newly-loaded [MusicStore.Library]. + * @throws NoPermissionException If [PERMISSION_READ_AUDIO] was not granted. + * @throws NoMusicException If no music was found on the device. */ private suspend fun indexImpl(context: Context, withCache: Boolean): MusicStore.Library { if (ContextCompat.checkSelfPermission(context, PERMISSION_READ_AUDIO) == @@ -227,7 +229,8 @@ class Indexer private constructor() { val metadataExtractor = MetadataExtractor(context, mediaStoreExtractor) - val songs = buildSongs(metadataExtractor, Settings(context)) + val songs = + buildSongs(metadataExtractor, Settings(context)).ifEmpty { throw NoMusicException() } // Build the rest of the music library from the song list. This is much more powerful // and reliable compared to using MediaStore to obtain grouping information. val buildStart = System.currentTimeMillis() @@ -380,7 +383,7 @@ class Indexer private constructor() { * Emit a new [State.Complete] state. This can be used to signal the completion of the music * loading process to external code. Will check if the callee has not been canceled and thus has * the ability to emit a new state - * @param result The new [Response] to emit, representing the outcome of the music loading + * @param result The new [Result] to emit, representing the outcome of the music loading * process. */ private suspend fun emitCompletion(result: Result) { @@ -442,6 +445,12 @@ class Indexer private constructor() { get() = "Not granted permissions to load music library" } + /** Thrown when no music was found on the device. */ + class NoMusicException : Exception() { + override val message: String + get() = "Unable to find any music" + } + /** * A listener for rapid-fire changes in the music loading state. * diff --git a/app/src/main/java/org/oxycblt/auxio/ui/CoordinatorAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/CoordinatorAppBarLayout.kt index 8504cd026..3a14709b5 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/CoordinatorAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/CoordinatorAppBarLayout.kt @@ -68,14 +68,14 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr } /** - * Expand this [AppBarLayout] with respect to the given [RecyclerView], preventing it from - * jumping around. - * @param recycler [RecyclerView] to expand with, or null if one is currently unavailable. + * Expand this [AppBarLayout] with respect to the current [RecyclerView] at + * [liftOnScrollTargetViewId], preventing it from jumping around. */ - fun expandWithRecycler(recycler: RecyclerView?) { - // TODO: Is it possible to use liftOnScrollTargetViewId to avoid the RecyclerView arg? + fun expandWithScrollingRecycler() { setExpanded(true) - recycler?.let { addOnOffsetChangedListener(ExpansionHackListener(it)) } + (findScrollingChild() as? RecyclerView)?.let { + addOnOffsetChangedListener(ExpansionHackListener(it)) + } } override fun onDetachedFromWindow() {