Twelve: Add loading progress bar to now playing fragment

Change-Id: I8a228dab1f39ed9cf3340198b0b5b5cee1a9f40f
diff --git a/app/src/main/java/org/lineageos/twelve/ext/Player.kt b/app/src/main/java/org/lineageos/twelve/ext/Player.kt
index 9ba4891..b7e525b 100644
--- a/app/src/main/java/org/lineageos/twelve/ext/Player.kt
+++ b/app/src/main/java/org/lineageos/twelve/ext/Player.kt
@@ -13,6 +13,7 @@
 import androidx.media3.common.Tracks
 import androidx.media3.common.util.UnstableApi
 import kotlinx.coroutines.channels.awaitClose
+import org.lineageos.twelve.models.PlaybackState
 import org.lineageos.twelve.models.RepeatMode
 
 @OptIn(UnstableApi::class)
@@ -46,6 +47,21 @@
     }
 }
 
+fun Player.playbackStateFlow() = conflatedCallbackFlow {
+    val listener = object : Player.Listener {
+        override fun onPlaybackStateChanged(playbackState: Int) {
+            trySend(typedPlaybackState)
+        }
+    }
+
+    addListener(listener)
+    trySend(typedPlaybackState)
+
+    awaitClose {
+        removeListener(listener)
+    }
+}
+
 fun Player.isPlayingFlow() = conflatedCallbackFlow {
     val listener = object : Player.Listener {
         override fun onIsPlayingChanged(isPlaying: Boolean) {
@@ -150,3 +166,12 @@
             RepeatMode.ALL -> Player.REPEAT_MODE_ALL
         }
     }
+
+val Player.typedPlaybackState: PlaybackState
+    get() = when (playbackState) {
+        Player.STATE_IDLE -> PlaybackState.IDLE
+        Player.STATE_BUFFERING -> PlaybackState.BUFFERING
+        Player.STATE_READY -> PlaybackState.READY
+        Player.STATE_ENDED -> PlaybackState.ENDED
+        else -> throw Exception("Unknown playback state")
+    }
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/NowPlayingFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/NowPlayingFragment.kt
index 58962a1..28c3ed4 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/NowPlayingFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/NowPlayingFragment.kt
@@ -35,12 +35,14 @@
 import com.google.android.material.appbar.MaterialToolbar
 import com.google.android.material.button.MaterialButton
 import com.google.android.material.card.MaterialCardView
+import com.google.android.material.progressindicator.LinearProgressIndicator
 import com.google.android.material.slider.Slider
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.R
 import org.lineageos.twelve.TwelveApplication
 import org.lineageos.twelve.ext.getViewProperty
+import org.lineageos.twelve.models.PlaybackState
 import org.lineageos.twelve.models.RepeatMode
 import org.lineageos.twelve.utils.TimestampFormatter
 import org.lineageos.twelve.viewmodels.NowPlayingViewModel
@@ -66,6 +68,7 @@
     private val equalizerMaterialButton by getViewProperty<MaterialButton>(R.id.equalizerMaterialButton)
     private val fileTypeMaterialCardView by getViewProperty<MaterialCardView>(R.id.fileTypeMaterialCardView)
     private val fileTypeTextView by getViewProperty<TextView>(R.id.fileTypeTextView)
+    private val linearProgressIndicator by getViewProperty<LinearProgressIndicator>(R.id.linearProgressIndicator)
     private val moreMaterialButton by getViewProperty<MaterialButton>(R.id.moreMaterialButton)
     private val nestedScrollView by getViewProperty<NestedScrollView>(R.id.nestedScrollView)
     private val nextTrackMaterialButton by getViewProperty<MaterialButton>(R.id.nextTrackMaterialButton)
@@ -193,6 +196,14 @@
                 }
 
                 launch {
+                    viewModel.playbackState.collectLatest { playbackState ->
+                        playbackState?.let {
+                            linearProgressIndicator.isVisible = it == PlaybackState.BUFFERING
+                        }
+                    }
+                }
+
+                launch {
                     viewModel.mediaItem.collectLatest { mediaItem ->
                         addOrRemoveFromPlaylistsMaterialButton.setOnClickListener {
                             mediaItem?.localConfiguration?.uri?.let { uri ->
diff --git a/app/src/main/java/org/lineageos/twelve/models/PlaybackState.kt b/app/src/main/java/org/lineageos/twelve/models/PlaybackState.kt
new file mode 100644
index 0000000..998b71c
--- /dev/null
+++ b/app/src/main/java/org/lineageos/twelve/models/PlaybackState.kt
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.twelve.models
+
+/**
+ * Player playback status.
+ */
+enum class PlaybackState {
+    IDLE,
+    BUFFERING,
+    READY,
+    ENDED,
+}
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
index 8ca33ff..fd3ae41 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
@@ -28,6 +28,7 @@
 import org.lineageos.twelve.ext.mediaMetadataFlow
 import org.lineageos.twelve.ext.next
 import org.lineageos.twelve.ext.playbackParametersFlow
+import org.lineageos.twelve.ext.playbackStateFlow
 import org.lineageos.twelve.ext.repeatModeFlow
 import org.lineageos.twelve.ext.shuffleModeFlow
 import org.lineageos.twelve.ext.tracksFlow
@@ -71,6 +72,17 @@
         )
 
     @OptIn(ExperimentalCoroutinesApi::class)
+    val playbackState = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.playbackStateFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
     val isPlaying = mediaController
         .filterNotNull()
         .flatMapLatest { it.isPlayingFlow() }
diff --git a/app/src/main/res/layout/fragment_now_playing.xml b/app/src/main/res/layout/fragment_now_playing.xml
index 0f81781..09488ab 100644
--- a/app/src/main/res/layout/fragment_now_playing.xml
+++ b/app/src/main/res/layout/fragment_now_playing.xml
@@ -67,6 +67,16 @@
             android:layout_height="wrap_content"
             android:orientation="vertical">
 
+            <com.google.android.material.progressindicator.LinearProgressIndicator
+                android:id="@+id/linearProgressIndicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:indeterminate="true"
+                android:visibility="gone"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
             <com.google.android.material.card.MaterialCardView
                 android:id="@+id/albumArtMaterialCardView"
                 style="@style/Widget.Material3.CardView.Filled"