Twelve: Move now playing stuff to the proper view model

Change-Id: I8fc3b99e2b1ada64590bea6393e2650d45274c4b
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingStatsViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingStatsViewModel.kt
index 8c28d66..d971feb 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingStatsViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingStatsViewModel.kt
@@ -24,7 +24,7 @@
 import org.lineageos.twelve.services.ProxyAudioProcessor
 import org.lineageos.twelve.services.ProxyDefaultAudioTrackBufferSizeProvider
 
-class NowPlayingStatsViewModel(application: Application) : TwelveViewModel(application) {
+class NowPlayingStatsViewModel(application: Application) : NowPlayingViewModel(application) {
     /**
      * [AudioStreamInformation] parsed from the currently selected audio track returned by the
      * player.
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 4421d47..8ca33ff 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
@@ -6,10 +6,35 @@
 package org.lineageos.twelve.viewmodels
 
 import android.app.Application
+import androidx.lifecycle.viewModelScope
+import androidx.media3.common.C
+import androidx.media3.common.MediaMetadata
+import androidx.media3.common.MimeTypes
+import androidx.media3.common.util.UnstableApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import org.lineageos.twelve.ext.availableCommandsFlow
+import org.lineageos.twelve.ext.isPlayingFlow
+import org.lineageos.twelve.ext.mediaItemFlow
+import org.lineageos.twelve.ext.mediaMetadataFlow
 import org.lineageos.twelve.ext.next
+import org.lineageos.twelve.ext.playbackParametersFlow
+import org.lineageos.twelve.ext.repeatModeFlow
+import org.lineageos.twelve.ext.shuffleModeFlow
+import org.lineageos.twelve.ext.tracksFlow
 import org.lineageos.twelve.ext.typedRepeatMode
+import org.lineageos.twelve.models.RepeatMode
 
-class NowPlayingViewModel(application: Application) : TwelveViewModel(application) {
+open class NowPlayingViewModel(application: Application) : TwelveViewModel(application) {
     enum class PlaybackSpeed(val value: Float) {
         ONE(1f),
         ONE_POINT_FIVE(1.5f),
@@ -23,6 +48,168 @@
         }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val mediaMetadata = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.mediaMetadataFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = MediaMetadata.EMPTY
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val mediaItem = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.mediaItemFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val isPlaying = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.isPlayingFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = false
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val shuffleMode = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.shuffleModeFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = false
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val repeatMode = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.repeatModeFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = RepeatMode.NONE
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val playbackParameters = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.playbackParametersFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    @androidx.annotation.OptIn(UnstableApi::class)
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val currentTrackFormat = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.tracksFlow() }
+        .flowOn(Dispatchers.Main)
+        .mapLatest { tracks ->
+            val groups = tracks.groups.filter { group ->
+                group.type == C.TRACK_TYPE_AUDIO && group.isSelected
+            }
+
+            require(groups.size <= 1) { "More than one audio track selected" }
+
+            groups.firstOrNull()?.let { group ->
+                (0..group.length).firstNotNullOfOrNull { i ->
+                    when (group.isTrackSelected(i)) {
+                        true -> group.getTrackFormat(i)
+                        false -> null
+                    }
+                }
+            }
+        }
+        .flowOn(Dispatchers.IO)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    val mimeType = combine(currentTrackFormat, mediaItem) { format, mediaItem ->
+        format?.sampleMimeType
+            ?: format?.containerMimeType
+            ?: mediaItem?.localConfiguration?.mimeType
+    }
+        .flowOn(Dispatchers.IO)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    @androidx.annotation.OptIn(UnstableApi::class)
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val displayFileType = mimeType
+        .mapLatest { mimeType ->
+            mimeType?.let {
+                MimeTypes.normalizeMimeType(it)
+            }?.let {
+                it.takeIf { it.contains('/') }
+                    ?.substringAfterLast('/')
+                    ?.uppercase()
+            }
+        }
+        .flowOn(Dispatchers.IO)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val availableCommands = mediaController
+        .filterNotNull()
+        .flatMapLatest { it.availableCommandsFlow() }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val durationCurrentPositionMs = mediaController
+        .filterNotNull()
+        .flatMapLatest { mediaController ->
+            flow {
+                while (true) {
+                    val duration = mediaController.duration.takeIf { it != C.TIME_UNSET }
+                    emit(
+                        Triple(
+                            duration,
+                            duration?.let { mediaController.currentPosition },
+                            mediaController.playbackParameters.speed,
+                        )
+                    )
+                    delay(200)
+                }
+            }
+        }
+        .flowOn(Dispatchers.Main)
+        .stateIn(
+            viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = Triple(null, null, 1f)
+        )
+
     fun togglePlayPause() {
         mediaController.value?.let {
             if (it.isPlaying) {
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/TwelveViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/TwelveViewModel.kt
index 1020766..0926267 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/TwelveViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/TwelveViewModel.kt
@@ -9,36 +9,17 @@
 import android.content.ComponentName
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import androidx.media3.common.C
-import androidx.media3.common.MediaMetadata
-import androidx.media3.common.MimeTypes
-import androidx.media3.common.util.UnstableApi
 import androidx.media3.session.MediaController
 import androidx.media3.session.SessionToken
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.channelFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.guava.await
 import org.lineageos.twelve.TwelveApplication
 import org.lineageos.twelve.ext.applicationContext
-import org.lineageos.twelve.ext.availableCommandsFlow
-import org.lineageos.twelve.ext.isPlayingFlow
-import org.lineageos.twelve.ext.mediaItemFlow
-import org.lineageos.twelve.ext.mediaMetadataFlow
-import org.lineageos.twelve.ext.playbackParametersFlow
-import org.lineageos.twelve.ext.repeatModeFlow
-import org.lineageos.twelve.ext.shuffleModeFlow
-import org.lineageos.twelve.ext.tracksFlow
 import org.lineageos.twelve.ext.typedRepeatMode
 import org.lineageos.twelve.models.Audio
 import org.lineageos.twelve.models.RepeatMode
@@ -81,168 +62,6 @@
             initialValue = null
         )
 
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val mediaMetadata = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.mediaMetadataFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = MediaMetadata.EMPTY
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val mediaItem = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.mediaItemFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val isPlaying = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.isPlayingFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = false
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val shuffleMode = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.shuffleModeFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = false
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val repeatMode = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.repeatModeFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = RepeatMode.NONE
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val playbackParameters = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.playbackParametersFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    @androidx.annotation.OptIn(UnstableApi::class)
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val currentTrackFormat = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.tracksFlow() }
-        .flowOn(Dispatchers.Main)
-        .mapLatest { tracks ->
-            val groups = tracks.groups.filter { group ->
-                group.type == C.TRACK_TYPE_AUDIO && group.isSelected
-            }
-
-            require(groups.size <= 1) { "More than one audio track selected" }
-
-            groups.firstOrNull()?.let { group ->
-                (0..group.length).firstNotNullOfOrNull { i ->
-                    when (group.isTrackSelected(i)) {
-                        true -> group.getTrackFormat(i)
-                        false -> null
-                    }
-                }
-            }
-        }
-        .flowOn(Dispatchers.IO)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    val mimeType = combine(currentTrackFormat, mediaItem) { format, mediaItem ->
-        format?.sampleMimeType
-            ?: format?.containerMimeType
-            ?: mediaItem?.localConfiguration?.mimeType
-    }
-        .flowOn(Dispatchers.IO)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    @androidx.annotation.OptIn(UnstableApi::class)
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val displayFileType = mimeType
-        .mapLatest { mimeType ->
-            mimeType?.let {
-                MimeTypes.normalizeMimeType(it)
-            }?.let {
-                it.takeIf { it.contains('/') }
-                    ?.substringAfterLast('/')
-                    ?.uppercase()
-            }
-        }
-        .flowOn(Dispatchers.IO)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val availableCommands = mediaController
-        .filterNotNull()
-        .flatMapLatest { it.availableCommandsFlow() }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val durationCurrentPositionMs = mediaController
-        .filterNotNull()
-        .flatMapLatest { mediaController ->
-            flow {
-                while (true) {
-                    val duration = mediaController.duration.takeIf { it != C.TIME_UNSET }
-                    emit(
-                        Triple(
-                            duration,
-                            duration?.let { mediaController.currentPosition },
-                            mediaController.playbackParameters.speed,
-                        )
-                    )
-                    delay(200)
-                }
-            }
-        }
-        .flowOn(Dispatchers.Main)
-        .stateIn(
-            viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = Triple(null, null, 1f)
-        )
-
     fun playAudio(audio: List<Audio>, position: Int) {
         mediaController.value?.apply {
             // Reset playback settings