When joining a call, wait for the content_loaded action (#5399)

* When joining a call, wait for the `content_loaded` action

This ensures the widget won't be disposed when the user joins a call and loads the lobby screen for room calls
This commit is contained in:
Jorge Martin Espinosa 2025-09-23 17:18:53 +02:00 committed by GitHub
parent 3a3dce150a
commit b75b79bf40
3 changed files with 13 additions and 10 deletions

View file

@ -41,5 +41,8 @@ data class WidgetMessage(
@SerialName("send_event") @SerialName("send_event")
SendEvent, SendEvent,
@SerialName("content_loaded")
ContentLoaded,
} }
} }

View file

@ -79,7 +79,7 @@ class CallScreenPresenter(
val urlState = remember { mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized) } val urlState = remember { mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized) }
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) } val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) } val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
var isJoinedCall by rememberSaveable { mutableStateOf(false) } var isWidgetLoaded by rememberSaveable { mutableStateOf(false) }
var ignoreWebViewError by rememberSaveable { mutableStateOf(false) } var ignoreWebViewError by rememberSaveable { mutableStateOf(false) }
var webViewError by remember { mutableStateOf<String?>(null) } var webViewError by remember { mutableStateOf<String?>(null) }
val languageTag = languageTagProvider.provideLanguageTag() val languageTag = languageTagProvider.provideLanguageTag()
@ -139,8 +139,8 @@ class CallScreenPresenter(
if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget) { if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget) {
if (parsedMessage.action == WidgetMessage.Action.Close) { if (parsedMessage.action == WidgetMessage.Action.Close) {
close(callWidgetDriver.value, navigator) close(callWidgetDriver.value, navigator)
} else if (parsedMessage.action == WidgetMessage.Action.Join) { } else if (parsedMessage.action == WidgetMessage.Action.ContentLoaded) {
isJoinedCall = true isWidgetLoaded = true
} }
} }
} }
@ -151,8 +151,8 @@ class CallScreenPresenter(
// Wait for the call to be joined, if it takes too long, we display an error // Wait for the call to be joined, if it takes too long, we display an error
delay(10.seconds) delay(10.seconds)
if (!isJoinedCall) { if (!isWidgetLoaded) {
Timber.w("The call took too long to be joined. Displaying an error before exiting.") Timber.w("The call took too long to load. Displaying an error before exiting.")
// This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call // This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
webViewError = "" webViewError = ""
@ -165,10 +165,10 @@ class CallScreenPresenter(
is CallScreenEvents.Hangup -> { is CallScreenEvents.Hangup -> {
val widgetId = callWidgetDriver.value?.id val widgetId = callWidgetDriver.value?.id
val interceptor = messageInterceptor.value val interceptor = messageInterceptor.value
if (widgetId != null && interceptor != null && isJoinedCall) { if (widgetId != null && interceptor != null && isWidgetLoaded) {
// If the call was joined, we need to hang up first. Then the UI will be dismissed automatically. // If the call was joined, we need to hang up first. Then the UI will be dismissed automatically.
sendHangupMessage(widgetId, interceptor) sendHangupMessage(widgetId, interceptor)
isJoinedCall = false isWidgetLoaded = false
coroutineScope.launch { coroutineScope.launch {
// Wait for a couple of seconds to receive the hangup message // Wait for a couple of seconds to receive the hangup message
@ -198,7 +198,7 @@ class CallScreenPresenter(
urlState = urlState.value, urlState = urlState.value,
webViewError = webViewError, webViewError = webViewError,
userAgent = userAgent, userAgent = userAgent,
isCallActive = isJoinedCall, isCallActive = isWidgetLoaded,
isInWidgetMode = isInWidgetMode, isInWidgetMode = isInWidgetMode,
eventSink = { handleEvents(it) }, eventSink = { handleEvents(it) },
) )

View file

@ -215,7 +215,7 @@ import kotlin.time.Duration.Companion.seconds
} }
@Test @Test
fun `present - a received 'joined' action makes the call to be active`() = runTest { fun `present - a received 'content loaded' action makes the call to be active`() = runTest {
val navigator = FakeCallScreenNavigator() val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver() val widgetDriver = FakeMatrixWidgetDriver()
val presenter = createCallScreenPresenter( val presenter = createCallScreenPresenter(
@ -238,7 +238,7 @@ import kotlin.time.Duration.Companion.seconds
messageInterceptor.givenInterceptedMessage( messageInterceptor.givenInterceptedMessage(
""" """
{ {
"action":"io.element.join", "action":"content_loaded",
"api":"fromWidget", "api":"fromWidget",
"widgetId":"1", "widgetId":"1",
"requestId":"1" "requestId":"1"