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.
This commit is contained in:
Alexander Capehart 2023-01-01 17:01:22 -07:00
parent 9ab729a069
commit 4db4b023d5
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 55 additions and 32 deletions

View file

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

View file

@ -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<MusicStore.Library>) {
@ -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.
*

View file

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