PictureInPictureEvents -> PictureInPictureEvent

This commit is contained in:
Benoit Marty 2026-04-27 17:04:49 +02:00
parent 38f09aac95
commit f511023507
7 changed files with 25 additions and 25 deletions

View file

@ -10,8 +10,8 @@ package io.element.android.features.call.impl.pip
import io.element.android.features.call.impl.utils.PipController import io.element.android.features.call.impl.utils.PipController
sealed interface PictureInPictureEvents { sealed interface PictureInPictureEvent {
data class SetPipController(val pipController: PipController) : PictureInPictureEvents data class SetPipController(val pipController: PipController) : PictureInPictureEvent
data object EnterPictureInPicture : PictureInPictureEvents data object EnterPictureInPicture : PictureInPictureEvent
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvents data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvent
} }

View file

@ -36,17 +36,17 @@ class PictureInPicturePresenter(
var isInPictureInPicture by remember { mutableStateOf(false) } var isInPictureInPicture by remember { mutableStateOf(false) }
var pipController by remember { mutableStateOf<PipController?>(null) } var pipController by remember { mutableStateOf<PipController?>(null) }
fun handleEvent(event: PictureInPictureEvents) { fun handleEvent(event: PictureInPictureEvent) {
when (event) { when (event) {
is PictureInPictureEvents.SetPipController -> { is PictureInPictureEvent.SetPipController -> {
pipController = event.pipController pipController = event.pipController
} }
PictureInPictureEvents.EnterPictureInPicture -> { PictureInPictureEvent.EnterPictureInPicture -> {
coroutineScope.launch { coroutineScope.launch {
switchToPip(pipController) switchToPip(pipController)
} }
} }
is PictureInPictureEvents.OnPictureInPictureModeChanged -> { is PictureInPictureEvent.OnPictureInPictureModeChanged -> {
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}") Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}")
isInPictureInPicture = event.isInPip isInPictureInPicture = event.isInPip
if (event.isInPip) { if (event.isInPip) {

View file

@ -11,5 +11,5 @@ package io.element.android.features.call.impl.pip
data class PictureInPictureState( data class PictureInPictureState(
val supportPip: Boolean, val supportPip: Boolean,
val isInPictureInPicture: Boolean, val isInPictureInPicture: Boolean,
val eventSink: (PictureInPictureEvents) -> Unit, val eventSink: (PictureInPictureEvent) -> Unit,
) )

View file

@ -11,7 +11,7 @@ package io.element.android.features.call.impl.pip
fun aPictureInPictureState( fun aPictureInPictureState(
supportPip: Boolean = false, supportPip: Boolean = false,
isInPictureInPicture: Boolean = false, isInPictureInPicture: Boolean = false,
eventSink: (PictureInPictureEvents) -> Unit = {}, eventSink: (PictureInPictureEvent) -> Unit = {},
): PictureInPictureState { ): PictureInPictureState {
return PictureInPictureState( return PictureInPictureState(
supportPip = supportPip, supportPip = supportPip,

View file

@ -33,7 +33,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import io.element.android.features.call.impl.R import io.element.android.features.call.impl.R
import io.element.android.features.call.impl.pip.PictureInPictureEvents import io.element.android.features.call.impl.pip.PictureInPictureEvent
import io.element.android.features.call.impl.pip.PictureInPictureState import io.element.android.features.call.impl.pip.PictureInPictureState
import io.element.android.features.call.impl.pip.aPictureInPictureState import io.element.android.features.call.impl.pip.aPictureInPictureState
import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason
@ -66,7 +66,7 @@ internal fun CallScreenView(
) { ) {
fun handleBack() { fun handleBack() {
if (pipState.supportPip) { if (pipState.supportPip) {
pipState.eventSink.invoke(PictureInPictureEvents.EnterPictureInPicture) pipState.eventSink.invoke(PictureInPictureEvent.EnterPictureInPicture)
} else { } else {
state.eventSink(CallScreenEvent.Hangup) state.eventSink(CallScreenEvent.Hangup)
} }
@ -132,7 +132,7 @@ internal fun CallScreenView(
) )
state.eventSink(CallScreenEvent.SetupMessageChannels(interceptor)) state.eventSink(CallScreenEvent.SetupMessageChannels(interceptor))
val pipController = WebViewPipController(webView) val pipController = WebViewPipController(webView)
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController)) pipState.eventSink(PictureInPictureEvent.SetPipController(pipController))
}, },
onDestroyWebView = { onDestroyWebView = {
// Reset audio mode // Reset audio mode

View file

@ -38,7 +38,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark
import io.element.android.features.call.api.CallData import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.DefaultElementCallEntryPoint
import io.element.android.features.call.impl.di.CallBindings import io.element.android.features.call.impl.di.CallBindings
import io.element.android.features.call.impl.pip.PictureInPictureEvents import io.element.android.features.call.impl.pip.PictureInPictureEvent
import io.element.android.features.call.impl.pip.PictureInPicturePresenter import io.element.android.features.call.impl.pip.PictureInPicturePresenter
import io.element.android.features.call.impl.pip.PictureInPictureState import io.element.android.features.call.impl.pip.PictureInPictureState
import io.element.android.features.call.impl.pip.PipView import io.element.android.features.call.impl.pip.PipView
@ -159,7 +159,7 @@ class ElementCallActivity :
if (requestPermissionCallback != null) { if (requestPermissionCallback != null) {
Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions") Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
} else { } else {
pipEventSink(PictureInPictureEvents.EnterPictureInPicture) pipEventSink(PictureInPictureEvent.EnterPictureInPicture)
} }
} }
addOnUserLeaveHintListener(listener) addOnUserLeaveHintListener(listener)
@ -169,7 +169,7 @@ class ElementCallActivity :
} }
DisposableEffect(Unit) { DisposableEffect(Unit) {
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo -> val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
pipEventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(isInPictureInPictureMode)) pipEventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(isInPictureInPictureMode))
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call") Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
eventSink?.invoke(CallScreenEvent.Hangup) eventSink?.invoke(CallScreenEvent.Hangup)

View file

@ -58,13 +58,13 @@ class PictureInPicturePresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.isInPictureInPicture).isFalse() assertThat(initialState.isInPictureInPicture).isFalse()
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture) initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
enterPipModeResult.assertions().isCalledOnce() enterPipModeResult.assertions().isCalledOnce()
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true)) initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
val pipState = awaitItem() val pipState = awaitItem()
assertThat(pipState.isInPictureInPicture).isTrue() assertThat(pipState.isInPictureInPicture).isTrue()
// User stops pip // User stops pip
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false)) initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.isInPictureInPicture).isFalse() assertThat(finalState.isInPictureInPicture).isFalse()
} }
@ -84,8 +84,8 @@ class PictureInPicturePresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(PictureInPictureEvents.SetPipController(FakePipController(canEnterPipResult = { false }))) initialState.eventSink(PictureInPictureEvent.SetPipController(FakePipController(canEnterPipResult = { false })))
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture) initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
handUpResult.assertions().isCalledOnce() handUpResult.assertions().isCalledOnce()
} }
} }
@ -107,7 +107,7 @@ class PictureInPicturePresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink( initialState.eventSink(
PictureInPictureEvents.SetPipController( PictureInPictureEvent.SetPipController(
FakePipController( FakePipController(
canEnterPipResult = { true }, canEnterPipResult = { true },
enterPipResult = enterPipResult, enterPipResult = enterPipResult,
@ -115,16 +115,16 @@ class PictureInPicturePresenterTest {
) )
) )
) )
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture) initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
enterPipModeResult.assertions().isCalledOnce() enterPipModeResult.assertions().isCalledOnce()
enterPipResult.assertions().isNeverCalled() enterPipResult.assertions().isNeverCalled()
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true)) initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
val pipState = awaitItem() val pipState = awaitItem()
assertThat(pipState.isInPictureInPicture).isTrue() assertThat(pipState.isInPictureInPicture).isTrue()
enterPipResult.assertions().isCalledOnce() enterPipResult.assertions().isCalledOnce()
// User stops pip // User stops pip
exitPipResult.assertions().isNeverCalled() exitPipResult.assertions().isNeverCalled()
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false)) initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.isInPictureInPicture).isFalse() assertThat(finalState.isInPictureInPicture).isFalse()
exitPipResult.assertions().isCalledOnce() exitPipResult.assertions().isCalledOnce()