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:
parent
e1c55d5ddc
commit
4f8fc8008c
4 changed files with 80 additions and 75 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue