feat(nests): back gesture and Minimize button enter PiP instead of leaving
The only way to keep audio playing while leaving the room screen was the Home/Recents gesture (onUserLeaveHint). Back swiped the user out of the session entirely, and there was no in-UI affordance signalling that PiP existed. Users wanting to step away had no obvious path that preserved audio. - BackHandler in NestActivityContent: while connected and PIP is supported, back minimizes to PIP. Disconnected/Connecting/Failed fall through to the default finish() so cancelling still works. - Minimize IconButton in the room TopAppBar (hidden when the device doesn't advertise FEATURE_PICTURE_IN_PICTURE). - Public NestActivity.enterPip() consolidates the connected + feature guards so onUserLeaveHint, BackHandler, and the Minimize button all share one entry point. https://claude.ai/code/session_01BpwAxN9gs79CJuWKqf3BEB
This commit is contained in:
+18
-3
@@ -133,12 +133,16 @@ class NestActivity : AppCompatActivity() {
|
||||
registerReceiver(pipReceiver, filter)
|
||||
}
|
||||
|
||||
val pipSupported =
|
||||
packageManager.hasSystemFeature(android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
||||
|
||||
setContent {
|
||||
AmethystTheme {
|
||||
NestActivityContent(
|
||||
addressValue = addressValue,
|
||||
accountViewModel = accountViewModel,
|
||||
isInPipMode = isInPipMode.value,
|
||||
isPipSupported = pipSupported,
|
||||
onMuteState = { muted ->
|
||||
if (isMuted.value != muted) {
|
||||
isMuted.value = muted
|
||||
@@ -148,6 +152,7 @@ class NestActivity : AppCompatActivity() {
|
||||
onConnectedChange = { isConnected.value = it },
|
||||
pipMuteSignal = toggleMuteSignal,
|
||||
onLeave = { finish() },
|
||||
onMinimize = { enterPip() },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -155,9 +160,19 @@ class NestActivity : AppCompatActivity() {
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
super.onUserLeaveHint()
|
||||
// PIP only makes sense once the audio session is live; entering
|
||||
// PIP from the lobby/Connecting state would freeze a half-rendered
|
||||
// card in Recents. Also gates on the system supporting PIP.
|
||||
enterPip()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter Picture-in-Picture if the audio session is live and the
|
||||
* system supports PIP. Called both by [onUserLeaveHint] (Home /
|
||||
* Recents gesture) and by user-driven entry points (back gesture,
|
||||
* Minimize button in [com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.screen.NestFullScreen]).
|
||||
*
|
||||
* Gated on `isConnected` so PIP from the lobby/Connecting state
|
||||
* doesn't freeze a half-rendered card in Recents.
|
||||
*/
|
||||
fun enterPip() {
|
||||
if (!isConnected.value) return
|
||||
if (!packageManager.hasSystemFeature(android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE)) return
|
||||
runCatching { enterPictureInPictureMode(buildPipParams()) }
|
||||
|
||||
+17
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -28,6 +29,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.ConnectionUiState
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
@@ -58,10 +60,12 @@ internal fun NestActivityContent(
|
||||
addressValue: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
isInPipMode: Boolean,
|
||||
isPipSupported: Boolean,
|
||||
onMuteState: (Boolean) -> Unit,
|
||||
onConnectedChange: (Boolean) -> Unit,
|
||||
pipMuteSignal: SharedFlow<Unit>,
|
||||
onLeave: () -> Unit,
|
||||
onMinimize: () -> Unit,
|
||||
) {
|
||||
val parsedAddress = remember(addressValue) { Address.parse(addressValue) }
|
||||
if (parsedAddress == null) {
|
||||
@@ -97,10 +101,12 @@ internal fun NestActivityContent(
|
||||
viewModel = viewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
isInPipMode = isInPipMode,
|
||||
isPipSupported = isPipSupported,
|
||||
onMuteState = onMuteState,
|
||||
onConnectedChange = onConnectedChange,
|
||||
pipMuteSignal = pipMuteSignal,
|
||||
onLeave = onLeave,
|
||||
onMinimize = onMinimize,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -125,10 +131,12 @@ private fun NestActivityBody(
|
||||
viewModel: NestViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
isInPipMode: Boolean,
|
||||
isPipSupported: Boolean,
|
||||
onMuteState: (Boolean) -> Unit,
|
||||
onConnectedChange: (Boolean) -> Unit,
|
||||
pipMuteSignal: SharedFlow<Unit>,
|
||||
onLeave: () -> Unit,
|
||||
onMinimize: () -> Unit,
|
||||
) {
|
||||
val account = accountViewModel.account
|
||||
val localPubkey = account.signer.pubKey
|
||||
@@ -177,6 +185,13 @@ private fun NestActivityBody(
|
||||
handRaised = handRaised,
|
||||
)
|
||||
|
||||
// Back gesture while connected = minimize to PIP rather than tearing
|
||||
// down the audio session. When disconnected (lobby / Connecting /
|
||||
// Failed) or PIP isn't supported, we let back fall through to the
|
||||
// default Activity.finish() so the user can cancel out cleanly.
|
||||
val canMinimize = isPipSupported && !isInPipMode && ui.connection is ConnectionUiState.Connected
|
||||
BackHandler(enabled = canMinimize) { onMinimize() }
|
||||
|
||||
if (isInPipMode) {
|
||||
NestPipScreen(
|
||||
title = event.room(),
|
||||
@@ -195,6 +210,8 @@ private fun NestActivityBody(
|
||||
handRaised = handRaised,
|
||||
onHandRaisedChange = { handRaised = it },
|
||||
onLeave = onLeave,
|
||||
onMinimize = onMinimize,
|
||||
canMinimize = isPipSupported,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -114,6 +114,8 @@ internal fun NestFullScreen(
|
||||
handRaised: Boolean,
|
||||
onHandRaisedChange: (Boolean) -> Unit,
|
||||
onLeave: () -> Unit,
|
||||
onMinimize: () -> Unit,
|
||||
canMinimize: Boolean,
|
||||
) {
|
||||
var showEditSheet by rememberSaveable { mutableStateOf(false) }
|
||||
var showHostMenu by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -181,6 +183,8 @@ internal fun NestFullScreen(
|
||||
title = event.room().orEmpty(),
|
||||
isHost = isHost,
|
||||
showHostMenu = showHostMenu,
|
||||
showMinimize = canMinimize,
|
||||
onMinimize = onMinimize,
|
||||
onMenuOpen = { showHostMenu = true },
|
||||
onMenuDismiss = { showHostMenu = false },
|
||||
onShare = {
|
||||
@@ -483,6 +487,8 @@ private fun NestTopAppBar(
|
||||
title: String,
|
||||
isHost: Boolean,
|
||||
showHostMenu: Boolean,
|
||||
showMinimize: Boolean,
|
||||
onMinimize: () -> Unit,
|
||||
onMenuOpen: () -> Unit,
|
||||
onMenuDismiss: () -> Unit,
|
||||
onShare: () -> Unit,
|
||||
@@ -497,6 +503,18 @@ private fun NestTopAppBar(
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
// Minimize-to-PIP affordance. The user-facing way to learn
|
||||
// that audio keeps playing in a floating window — pairs
|
||||
// with the back-gesture handler in NestActivityContent.
|
||||
// Hidden when the device doesn't advertise the PIP feature.
|
||||
if (showMinimize) {
|
||||
IconButton(onClick = onMinimize) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.PictureInPicture,
|
||||
contentDescription = stringRes(R.string.nest_minimize_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
Box {
|
||||
IconButton(onClick = onMenuOpen) {
|
||||
Icon(
|
||||
|
||||
@@ -584,6 +584,8 @@
|
||||
<string name="nest_participant_mute">Mute</string>
|
||||
<string name="nest_participant_unmute">Unmute</string>
|
||||
<string name="nest_share_action">Share room</string>
|
||||
<string name="nest_minimize">Minimize</string>
|
||||
<string name="nest_minimize_description">Minimize to keep listening</string>
|
||||
<string name="nest_create_fab">Start space</string>
|
||||
<string name="nest_no_server_title">Set up a nest server</string>
|
||||
<string name="nest_no_server_body">You haven\'t picked a nest server yet. Add %1$s to your server list and continue?\n\nYou can change this later in Settings.</string>
|
||||
|
||||
Reference in New Issue
Block a user