Fix issues with stuck 'loading...' state and hangup (#1690)

* Fix issues with stuck 'loading...' state and hangu
This commit is contained in:
Jorge Martin Espinosa 2023-10-30 17:01:54 +01:00 committed by GitHub
parent 42029d6cd2
commit 22767031d9
5 changed files with 34 additions and 14 deletions

View file

@ -18,6 +18,7 @@ package io.element.android.features.call.data
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable @Serializable
data class WidgetMessage( data class WidgetMessage(
@ -25,6 +26,7 @@ data class WidgetMessage(
@SerialName("widgetId") val widgetId: String, @SerialName("widgetId") val widgetId: String,
@SerialName("requestId") val requestId: String, @SerialName("requestId") val requestId: String,
@SerialName("action") val action: Action, @SerialName("action") val action: Action,
@SerialName("data") val data: JsonElement? = null,
) { ) {
@Serializable @Serializable
@ -38,6 +40,8 @@ data class WidgetMessage(
@Serializable @Serializable
enum class Action { enum class Action {
@SerialName("im.vector.hangup") @SerialName("im.vector.hangup")
HangUp HangUp,
@SerialName("send_event")
SendEvent,
} }
} }

View file

@ -19,9 +19,12 @@ package io.element.android.features.call.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -41,6 +44,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import java.util.UUID import java.util.UUID
class CallScreenPresenter @AssistedInject constructor( class CallScreenPresenter @AssistedInject constructor(
@ -66,6 +72,7 @@ class CallScreenPresenter @AssistedInject constructor(
val urlState = remember { mutableStateOf<Async<String>>(Async.Uninitialized) } val urlState = remember { mutableStateOf<Async<String>>(Async.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) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
loadUrl(callType, urlState, callWidgetDriver) loadUrl(callType, urlState, callWidgetDriver)
@ -92,8 +99,16 @@ class CallScreenPresenter @AssistedInject constructor(
callWidgetDriver.value?.send(it) callWidgetDriver.value?.send(it)
val parsedMessage = parseMessage(it) val parsedMessage = parseMessage(it)
if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget && parsedMessage.action == WidgetMessage.Action.HangUp) { if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget) {
close(callWidgetDriver.value, navigator) if (parsedMessage.action == WidgetMessage.Action.HangUp) {
close(callWidgetDriver.value, navigator)
} else if (parsedMessage.action == WidgetMessage.Action.SendEvent) {
// This event is received when a member joins the call, the first one will be the current one
val type = parsedMessage.data?.jsonObject?.get("type")?.jsonPrimitive?.contentOrNull
if (type == "org.matrix.msc3401.call.member") {
isJoinedCall = true
}
}
} }
} }
.launchIn(this) .launchIn(this)
@ -105,11 +120,13 @@ class CallScreenPresenter @AssistedInject constructor(
is CallScreeEvents.Hangup -> { is CallScreeEvents.Hangup -> {
val widgetId = callWidgetDriver.value?.id val widgetId = callWidgetDriver.value?.id
val interceptor = messageInterceptor.value val interceptor = messageInterceptor.value
if (widgetId != null && interceptor != null) { if (widgetId != null && interceptor != null && isJoinedCall) {
// If the call was joined, we need to hang up first. Then the UI will be dismissed automatically.
sendHangupMessage(widgetId, interceptor) sendHangupMessage(widgetId, interceptor)
} } else {
coroutineScope.launch { coroutineScope.launch {
close(callWidgetDriver.value, navigator) close(callWidgetDriver.value, navigator)
}
} }
} }
is CallScreeEvents.SetupMessageChannels -> { is CallScreeEvents.SetupMessageChannels -> {
@ -159,6 +176,7 @@ class CallScreenPresenter @AssistedInject constructor(
widgetId = widgetId, widgetId = widgetId,
requestId = "widgetapi-${clock.epochMillis()}", requestId = "widgetapi-${clock.epochMillis()}",
action = WidgetMessage.Action.HangUp, action = WidgetMessage.Action.HangUp,
data = null,
) )
messageInterceptor.sendMessage(WidgetMessageSerializer.serialize(message)) messageInterceptor.sendMessage(WidgetMessageSerializer.serialize(message))
} }

View file

@ -111,12 +111,8 @@ private fun CallWebView(
modifier = modifier, modifier = modifier,
factory = { context -> factory = { context ->
WebView(context).apply { WebView(context).apply {
setup(userAgent, onPermissionsRequested)
if (url is Async.Success) {
loadUrl(url.data)
}
onWebViewCreated(this) onWebViewCreated(this)
setup(userAgent, onPermissionsRequested)
} }
}, },
update = { webView -> update = { webView ->

View file

@ -36,7 +36,8 @@ class WebViewWidgetMessageInterceptor(
const val LISTENER_NAME = "elementX" const val LISTENER_NAME = "elementX"
} }
override val interceptedMessages = MutableSharedFlow<String>(replay = 1, extraBufferCapacity = 2) // It's important to have extra capacity here to make sure we don't drop any messages
override val interceptedMessages = MutableSharedFlow<String>(extraBufferCapacity = 10)
init { init {
webView.webViewClient = object : WebViewClient() { webView.webViewClient = object : WebViewClient() {

View file

@ -36,7 +36,8 @@ class RustWidgetDriver(
private val widgetCapabilitiesProvider: WidgetCapabilitiesProvider, private val widgetCapabilitiesProvider: WidgetCapabilitiesProvider,
): MatrixWidgetDriver { ): MatrixWidgetDriver {
override val incomingMessages = MutableSharedFlow<String>() // It's important to have extra capacity here to make sure we don't drop any messages
override val incomingMessages = MutableSharedFlow<String>(extraBufferCapacity = 10)
private val driverAndHandle = makeWidgetDriver(widgetSettings.toRustWidgetSettings()) private val driverAndHandle = makeWidgetDriver(widgetSettings.toRustWidgetSettings())
private var receiveMessageJob: Job? = null private var receiveMessageJob: Job? = null