music: rework loading ui flow

Rework the UI flow for music loading to be more coherent with runtime
rescanning.

The loading progress is now shown as a card on the bottom of the
screen. This way, app use is not completely crippled when the app has
to rescan the music library, albeit the shuffle button still has to
be disabled during this period.
This commit is contained in:
OxygenCobalt 2022-07-03 14:47:10 -06:00
parent e1c55d5ddc
commit 4f8fc8008c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 80 additions and 75 deletions

View file

@ -98,7 +98,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
binding.homeToolbar.alpha = 1f - (abs(offset.toFloat()) / (range.toFloat() / 2))
binding.homeContent.updatePadding(
binding.homePager.updatePadding(
bottom = binding.homeAppbar.totalScrollRange + offset)
}
}
@ -107,7 +107,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
updateTabConfiguration()
binding.homeIndexingContainer.setOnApplyWindowInsetsListener { view, insets ->
binding.homeIndexingWrapper.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
insets
}
@ -141,7 +141,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
// --- VIEWMODEL SETUP ---
collect(homeModel.isFastScrolling, ::updateFastScrolling)
collect(homeModel.isFastScrolling) { updateFab() }
collect(homeModel.recreateTabs, ::handleRecreateTabs)
collectImmediately(homeModel.currentTab, ::updateCurrentTab)
collectImmediately(indexerModel.state, ::handleIndexerState)
@ -190,23 +190,6 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
return true
}
private fun updateFastScrolling(isFastScrolling: Boolean) {
val binding = requireBinding()
// Make sure an update here doesn't mess up the FAB state when it comes to the
// loader response.
val state = indexerModel.state.value
if (!(state is Indexer.State.Complete && state.response is Indexer.Response.Ok)) {
return
}
if (isFastScrolling) {
binding.homeFab.hide()
} else {
binding.homeFab.show()
}
}
private fun updateCurrentTab(tab: DisplayMode) {
// Make sure that we update the scrolling view and allowed menu items whenever
// the tab changes.
@ -274,27 +257,23 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
private fun handleIndexerState(state: Indexer.State?) {
val binding = requireBinding()
when (state) {
is Indexer.State.Complete -> handleIndexerResponse(binding, state.response)
is Indexer.State.Indexing -> handleIndexingState(binding, state.indexing)
null -> {
logD("Indexer is in indeterminate state")
binding.homeFab.hide()
binding.homeIndexingContainer.visibility = View.INVISIBLE
binding.homePager.visibility = View.INVISIBLE
}
}
updateFab()
}
private fun handleIndexerResponse(binding: FragmentHomeBinding, response: Indexer.Response) {
if (response is Indexer.Response.Ok) {
binding.homeFab.show()
binding.homeIndexingContainer.visibility = View.INVISIBLE
binding.homePager.visibility = View.VISIBLE
} else {
binding.homeFab.hide()
binding.homePager.visibility = View.INVISIBLE
binding.homeIndexingContainer.visibility = View.VISIBLE
logD("Received non-ok response $response")
@ -336,8 +315,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
}
private fun handleIndexingState(binding: FragmentHomeBinding, indexing: Indexer.Indexing) {
binding.homeFab.hide()
binding.homePager.visibility = View.INVISIBLE
updateFab()
binding.homeIndexingContainer.visibility = View.VISIBLE
binding.homeIndexingProgress.visibility = View.VISIBLE
binding.homeIndexingAction.visibility = View.INVISIBLE
@ -359,6 +337,18 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
}
}
private fun updateFab() {
val binding = requireBinding()
val fastScrolling = homeModel.isFastScrolling.value
val state = indexerModel.state.value
if (fastScrolling ||
!(state is Indexer.State.Complete && state.response is Indexer.Response.Ok)) {
binding.homeFab.hide()
} else {
binding.homeFab.show()
}
}
private fun handleNavigation(item: Music?) {
// Note: You will want to add a post call to this if you want to re-introduce a collapsing
// toolbar.

View file

@ -23,6 +23,7 @@ import android.content.pm.PackageManager
import android.database.Cursor
import android.os.Build
import androidx.core.content.ContextCompat
import kotlin.coroutines.cancellation.CancellationException
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.music.backend.Api21MediaStoreBackend
import org.oxycblt.auxio.music.backend.Api29MediaStoreBackend
@ -121,7 +122,7 @@ class Indexer {
this.callback = null
}
fun index(context: Context) {
suspend fun index(context: Context) {
val generation = synchronized(this) { ++currentGeneration }
val notGranted =
@ -181,7 +182,9 @@ class Indexer {
@Synchronized
private fun emitIndexing(indexing: Indexing?, generation: Long) {
if (currentGeneration != generation) {
return
// Not the running task anymore, cancel this co-routine
// We do this instead of using yield since it is *far* cheaper.
throw CancellationException()
}
indexingState = indexing
@ -198,7 +201,9 @@ class Indexer {
@Synchronized
private fun emitCompletion(response: Response, generation: Long) {
if (currentGeneration != generation) {
return
// Not the running task anymore, cancel this co-routine
// We do this instead of using yield since it is *far* cheaper.
throw CancellationException()
}
lastResponse = response

View file

@ -46,8 +46,6 @@ import org.oxycblt.auxio.util.logD
* @author OxygenCobalt
*
* TODO: Add file observing
*
* TODO: Rework UI flow once again
*/
class IndexerService : Service(), Indexer.Controller, Settings.Callback {
private val indexer = Indexer.getInstance()

View file

@ -29,60 +29,72 @@
</org.oxycblt.auxio.ui.coordinator.EdgeAppBarLayout>
<FrameLayout
android:id="@+id/home_content"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:layout="@layout/fragment_home_list" />
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
android:id="@+id/home_indexing_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:animateLayoutChanges="true"
android:clipToPadding="false">
<com.google.android.material.card.MaterialCardView
android:id="@+id/home_indexing_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:paddingStart="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium">
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_medium">
<TextView
android:id="@+id/home_indexing_status"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_medium"
android:gravity="center"
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
app:layout_constraintBottom_toTopOf="@+id/home_indexing_action"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Status" />
android:animateLayoutChanges="true">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/home_indexing_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:indeterminateAnimationType="disjoint"
app:layout_constraintBottom_toBottomOf="@+id/home_indexing_action"
app:layout_constraintTop_toTopOf="@+id/home_indexing_action"
app:trackColor="@color/sel_track" />
<TextView
android:id="@+id/home_indexing_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_medium"
android:gravity="center"
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
app:layout_constraintBottom_toTopOf="@+id/home_indexing_action"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Status" />
<Button
android:id="@+id/home_indexing_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lbl_retry"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/home_indexing_status" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/home_indexing_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:indeterminate="true"
app:indeterminateAnimationType="disjoint"
app:layout_constraintBottom_toBottomOf="@+id/home_indexing_action"
app:layout_constraintTop_toTopOf="@+id/home_indexing_action"
app:trackColor="@color/sel_track" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/home_indexing_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lbl_retry"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:layout_marginBottom="@dimen/spacing_medium"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/home_indexing_status" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:layout="@layout/fragment_home_list" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>