@@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting
1212import androidx.compose.foundation.background
1313import androidx.compose.foundation.layout.Arrangement
1414import androidx.compose.foundation.layout.Box
15+ import androidx.compose.foundation.layout.BoxScope
1516import androidx.compose.foundation.layout.Column
1617import androidx.compose.foundation.layout.Row
1718import androidx.compose.foundation.layout.Spacer
@@ -65,6 +66,7 @@ import io.homeassistant.companion.android.frontend.permissions.MultiplePermissio
6566import io.homeassistant.companion.android.frontend.permissions.NotificationPermissionPrompt
6667import io.homeassistant.companion.android.frontend.permissions.PermissionRequest
6768import io.homeassistant.companion.android.frontend.permissions.SinglePermissionEffect
69+ import io.homeassistant.companion.android.launch.PipReadiness
6870import io.homeassistant.companion.android.loading.LoadingScreen
6971import io.homeassistant.companion.android.onboarding.locationforsecureconnection.LocationForSecureConnectionScreen
7072import io.homeassistant.companion.android.onboarding.locationforsecureconnection.LocationForSecureConnectionViewModel
@@ -115,6 +117,7 @@ internal fun FrontendScreen(
115117 onSecurityLevelHelpClick : suspend () -> Unit ,
116118 onShowSnackbar : suspend (message: String , action: String? ) -> Boolean ,
117119 modifier : Modifier = Modifier ,
120+ onPipReadinessChanged : (PipReadiness ? ) -> Unit = {},
118121) {
119122 val viewState by viewModel.viewState.collectAsStateWithLifecycle()
120123 val pendingPermissionRequest by viewModel.pendingPermissionRequest.collectAsStateWithLifecycle()
@@ -170,6 +173,7 @@ internal fun FrontendScreen(
170173 onGesture = viewModel::onGesture,
171174 onExoPlayerFullscreenChanged = viewModel::onExoPlayerFullscreenChanged,
172175 autoPlayVideoEnabled = autoPlayVideoEnabled,
176+ onPipReadinessChanged = onPipReadinessChanged,
173177 modifier = modifier,
174178 )
175179}
@@ -204,6 +208,7 @@ internal fun FrontendScreenContent(
204208 webViewActions : Flow <WebViewAction > = emptyFlow(),
205209 onGesture : (GestureDirection , Int ) -> Unit = { _, _ -> },
206210 onExoPlayerFullscreenChanged : (Boolean ) -> Unit = {},
211+ onPipReadinessChanged : (PipReadiness ? ) -> Unit = {},
207212) {
208213 var webView by remember { mutableStateOf<WebView ?>(null ) }
209214
@@ -241,13 +246,13 @@ internal fun FrontendScreenContent(
241246 autoPlayVideoEnabled = autoPlayVideoEnabled,
242247 )
243248
244- ExoPlayerOverlay (
249+ PipEligibleOverlays (
245250 contentState = viewState as ? FrontendViewState .Content ,
246- onFullscreenChanged = onExoPlayerFullscreenChanged,
251+ customView = customView,
252+ onExoPlayerFullscreenChanged = onExoPlayerFullscreenChanged,
253+ onPipReadinessChanged = onPipReadinessChanged,
247254 )
248255
249- CustomViewOverlay (customView = customView)
250-
251256 StateOverlay (
252257 viewState = viewState,
253258 errorStateProvider = errorStateProvider,
@@ -582,19 +587,24 @@ private fun PendingPermissionHandler(pendingRequest: PermissionRequest?) {
582587 onDismiss = pendingRequest.onDismiss,
583588 )
584589 }
590+
585591 is PermissionRequest .MultiplePermissions -> {
586592 MultiplePermissionsEffect (
587593 pendingRequest = pendingRequest,
588594 onPermissionResult = pendingRequest.onResult,
589595 )
590596 }
597+
591598 is PermissionRequest .SinglePermission -> {
592599 SinglePermissionEffect (
593600 pendingRequest = pendingRequest,
594601 onPermissionResult = pendingRequest.onResult,
595602 )
596603 }
597- null -> { /* No pending permission */ }
604+
605+ null -> {
606+ /* No pending permission */
607+ }
598608 }
599609}
600610
@@ -635,6 +645,33 @@ private fun WebViewEffects(
635645 }
636646}
637647
648+ /* *
649+ * Renders PiP-eligible overlays and reports their combined [PipReadiness] to the host.
650+ */
651+ @Composable
652+ private fun BoxScope.PipEligibleOverlays (
653+ contentState : FrontendViewState .Content ? ,
654+ customView : View ? ,
655+ onExoPlayerFullscreenChanged : (Boolean ) -> Unit ,
656+ onPipReadinessChanged : (PipReadiness ? ) -> Unit ,
657+ ) {
658+ val exoState = contentState?.exoPlayerState
659+ val readiness = remember(customView, exoState?.isFullScreen, exoState?.videoAspectRatio) {
660+ PipReadiness .from(customViewShown = customView != null , exoState = exoState)
661+ }
662+
663+ LaunchedEffect (readiness) { onPipReadinessChanged(readiness) }
664+ DisposableEffect (Unit ) {
665+ onDispose { onPipReadinessChanged(null ) }
666+ }
667+
668+ ExoPlayerOverlay (
669+ contentState = contentState,
670+ onFullscreenChanged = onExoPlayerFullscreenChanged,
671+ )
672+ CustomViewOverlay (customView = customView)
673+ }
674+
638675@Composable
639676private fun CustomViewOverlay (customView : View ? ) {
640677 val view: View = customView ? : return
0 commit comments