open now playing when the media notification is tapped

This commit is contained in:
austinried
2026-10-04 00:30:07 +00:00
parent 6eb23445f1
commit 46ab8f66c4
4 changed files with 70 additions and 5 deletions
@@ -1,6 +1,7 @@
package com.subtracks
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@@ -8,11 +9,14 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.mutableIntStateOf
import androidx.core.content.ContextCompat
import com.subtracks.ui.SubtracksRoot
import com.subtracks.ui.theme.SubtracksTheme
class MainActivity : ComponentActivity() {
private val nowPlayingRequest = mutableIntStateOf(0)
private val requestNotifications =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { }
@@ -20,13 +24,20 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
requestNotificationPermission()
enableEdgeToEdge()
if (intent?.action == ACTION_OPEN_NOW_PLAYING) nowPlayingRequest.intValue++
setContent {
SubtracksTheme {
SubtracksRoot()
SubtracksRoot(nowPlayingRequest = nowPlayingRequest.intValue)
}
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
if (intent.action == ACTION_OPEN_NOW_PLAYING) nowPlayingRequest.intValue++
}
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
val granted =
@@ -34,4 +45,8 @@ class MainActivity : ComponentActivity() {
PackageManager.PERMISSION_GRANTED
if (!granted) requestNotifications.launch(Manifest.permission.POST_NOTIFICATIONS)
}
companion object {
const val ACTION_OPEN_NOW_PLAYING = "com.subtracks.action.OPEN_NOW_PLAYING"
}
}
@@ -129,7 +129,8 @@ class PlaybackService : MediaSessionService() {
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java),
Intent(this, MainActivity::class.java)
.setAction(MainActivity.ACTION_OPEN_NOW_PLAYING),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
),
).setBitmapLoader(loader)
@@ -166,6 +166,12 @@ internal fun resetOnSourceSwitch(
if (route in DETAIL_ROUTES) popDetail()
}
internal fun shouldOpenNowPlaying(
request: Int,
handled: Int,
playerVisible: Boolean,
): Boolean = playerVisible && request > handled
internal data class BackStackKey(
val route: String?,
val argument: String?,
@@ -181,7 +187,10 @@ internal fun focusPops(
}
@Composable
fun SubtracksRoot(root: RootViewModel = koinViewModel()) {
fun SubtracksRoot(
root: RootViewModel = koinViewModel(),
nowPlayingRequest: Int = 0,
) {
when (val hasSource = root.hasSource.collectAsStateWithLifecycle().value) {
null -> {
LoadingState()
@@ -195,14 +204,14 @@ fun SubtracksRoot(root: RootViewModel = koinViewModel()) {
val playbackController = koinInject<PlaybackController>()
LaunchedEffect(Unit) { playbackController.connect() }
val playbackReady by playbackController.ready.collectAsStateWithLifecycle()
if (playbackReady) MainNavigation() else LoadingState()
if (playbackReady) MainNavigation(nowPlayingRequest) else LoadingState()
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun MainNavigation() {
private fun MainNavigation(nowPlayingRequest: Int) {
val navController = rememberNavController()
val currentRoute =
navController
@@ -218,6 +227,7 @@ private fun MainNavigation() {
val downloads by downloadRepository.states().collectAsStateWithLifecycle()
var showingQueue by rememberSaveable { mutableStateOf(false) }
var nowPlayingOpen by rememberSaveable { mutableStateOf(false) }
var handledNowPlayingRequest by rememberSaveable { mutableStateOf(0) }
var nowPlayingProgress by remember { mutableFloatStateOf(0f) }
var miniPlayerTopPx by remember { mutableFloatStateOf(0f) }
var nowPlayingFadeOut by remember { mutableStateOf(false) }
@@ -302,6 +312,12 @@ private fun MainNavigation() {
nowPlayingProgress = 0f
}
}
LaunchedEffect(nowPlayingRequest, playerVisible) {
if (shouldOpenNowPlaying(nowPlayingRequest, handledNowPlayingRequest, playerVisible)) {
handledNowPlayingRequest = nowPlayingRequest
settleNowPlaying(true)
}
}
val density = LocalDensity.current
val navBarInset = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
val keyboardUp = WindowInsets.imeAnimationTarget.getBottom(density) > 0
@@ -0,0 +1,33 @@
package com.subtracks.ui
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class NowPlayingRequestTest {
@Test
fun aNotificationWithNothingPlayingDoesNotOpen() {
assertFalse(shouldOpenNowPlaying(request = 1, handled = 0, playerVisible = false))
}
@Test
fun aNotificationWithSomethingPlayingOpens() {
assertTrue(shouldOpenNowPlaying(request = 1, handled = 0, playerVisible = true))
}
@Test
fun anAlreadyHandledRequestDoesNotReopen() {
assertFalse(shouldOpenNowPlaying(request = 1, handled = 1, playerVisible = true))
}
@Test
fun aNewerRequestOpensAgain() {
assertTrue(shouldOpenNowPlaying(request = 2, handled = 1, playerVisible = true))
}
@Test
fun anOldRequestWaitsUntilSomethingIsPlaying() {
assertFalse(shouldOpenNowPlaying(request = 1, handled = 0, playerVisible = false))
assertTrue(shouldOpenNowPlaying(request = 1, handled = 0, playerVisible = true))
}
}