Handle no network error when starting Element Call.
This commit is contained in:
committed by
Benoit Marty
parent
7d3801a623
commit
0cea89edcc
@@ -11,6 +11,6 @@ import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
|
||||
|
||||
sealed interface CallScreenEvents {
|
||||
data object Hangup : CallScreenEvents
|
||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) :
|
||||
CallScreenEvents
|
||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvents
|
||||
data class OnWebViewError(val description: String?) : CallScreenEvents
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
@@ -38,6 +39,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.analytics.api.ScreenTracker
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -78,8 +80,10 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
||||
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
||||
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
||||
var canRenderWebViewInCaseOfError by rememberSaveable { mutableStateOf(false) }
|
||||
val languageTag = languageTagProvider.provideLanguageTag()
|
||||
val theme = if (ElementTheme.isLightTheme) "light" else "dark"
|
||||
val errorMessage = stringResource(id = CommonStrings.error_unknown)
|
||||
DisposableEffect(Unit) {
|
||||
coroutineScope.launch {
|
||||
// Sets the call as joined
|
||||
@@ -125,6 +129,8 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
LaunchedEffect(Unit) {
|
||||
interceptor.interceptedMessages
|
||||
.onEach {
|
||||
// We are receiving messages from the WebView, consider that the application is loaded
|
||||
canRenderWebViewInCaseOfError = true
|
||||
// Relay message to Widget Driver
|
||||
callWidgetDriver.value?.send(it)
|
||||
|
||||
@@ -163,11 +169,25 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
is CallScreenEvents.SetupMessageChannels -> {
|
||||
messageInterceptor.value = event.widgetMessageInterceptor
|
||||
}
|
||||
is CallScreenEvents.OnWebViewError -> {
|
||||
if (!canRenderWebViewInCaseOfError) {
|
||||
urlState.value = AsyncData.Failure(
|
||||
Exception(
|
||||
buildString {
|
||||
append(errorMessage)
|
||||
event.description?.let { append("\n\n").append(it) }
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
// Else ignore the error, give a chance the Element Call to recover by itself.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CallScreenState(
|
||||
urlState = urlState.value,
|
||||
canRenderWebViewInCaseOfError = canRenderWebViewInCaseOfError,
|
||||
userAgent = userAgent,
|
||||
isInWidgetMode = isInWidgetMode,
|
||||
eventSink = { handleEvents(it) },
|
||||
|
||||
@@ -11,6 +11,7 @@ import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
data class CallScreenState(
|
||||
val urlState: AsyncData<String>,
|
||||
val canRenderWebViewInCaseOfError: Boolean,
|
||||
val userAgent: String,
|
||||
val isInWidgetMode: Boolean,
|
||||
val eventSink: (CallScreenEvents) -> Unit,
|
||||
|
||||
@@ -21,12 +21,14 @@ open class CallScreenStateProvider : PreviewParameterProvider<CallScreenState> {
|
||||
|
||||
internal fun aCallScreenState(
|
||||
urlState: AsyncData<String> = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"),
|
||||
canRenderWebViewInCaseOfError: Boolean = true,
|
||||
userAgent: String = "",
|
||||
isInWidgetMode: Boolean = false,
|
||||
eventSink: (CallScreenEvents) -> Unit = {},
|
||||
): CallScreenState {
|
||||
return CallScreenState(
|
||||
urlState = urlState,
|
||||
canRenderWebViewInCaseOfError = canRenderWebViewInCaseOfError,
|
||||
userAgent = userAgent,
|
||||
isInWidgetMode = isInWidgetMode,
|
||||
eventSink = eventSink,
|
||||
|
||||
@@ -85,25 +85,30 @@ internal fun CallScreenView(
|
||||
BackHandler {
|
||||
handleBack()
|
||||
}
|
||||
CallWebView(
|
||||
modifier = Modifier
|
||||
if (state.urlState !is AsyncData.Failure || state.canRenderWebViewInCaseOfError) {
|
||||
CallWebView(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.fillMaxSize(),
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onWebViewCreate = { webView ->
|
||||
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||
val pipController = WebViewPipController(webView)
|
||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||
}
|
||||
)
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onWebViewCreate = { webView ->
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
webView = webView,
|
||||
onError = { state.eventSink(CallScreenEvents.OnWebViewError(it)) },
|
||||
)
|
||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||
val pipController = WebViewPipController(webView)
|
||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||
}
|
||||
)
|
||||
}
|
||||
when (state.urlState) {
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Loading ->
|
||||
|
||||
@@ -8,16 +8,23 @@
|
||||
package io.element.android.features.call.impl.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import io.element.android.features.call.impl.BuildConfig
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import timber.log.Timber
|
||||
|
||||
class WebViewWidgetMessageInterceptor(
|
||||
private val webView: WebView,
|
||||
private val onError: (String?) -> Unit,
|
||||
) : WidgetMessageInterceptor {
|
||||
companion object {
|
||||
// We call both the WebMessageListener and the JavascriptInterface objects in JS with this
|
||||
@@ -45,16 +52,35 @@ class WebViewWidgetMessageInterceptor(
|
||||
if (message.data.response && message.data.api == "toWidget"
|
||||
|| !message.data.response && message.data.api == "fromWidget") {
|
||||
let json = JSON.stringify(event.data)
|
||||
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG } }
|
||||
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG }}
|
||||
$LISTENER_NAME.postMessage(json);
|
||||
} else {
|
||||
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG } }
|
||||
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG }}
|
||||
}
|
||||
});
|
||||
""".trimIndent(),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
|
||||
// No network for instance, transmit the error
|
||||
Timber.e("onReceivedError error: ${error?.errorCode} ${error?.description}")
|
||||
onError(error?.description?.toString())
|
||||
super.onReceivedError(view, request, error)
|
||||
}
|
||||
|
||||
override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {
|
||||
Timber.e("onReceivedHttpError error: ${errorResponse?.statusCode} ${errorResponse?.reasonPhrase}")
|
||||
onError(errorResponse?.statusCode.toString())
|
||||
super.onReceivedHttpError(view, request, errorResponse)
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
|
||||
Timber.e("onReceivedSslError error: ${error?.primaryError}")
|
||||
onError(error?.primaryError?.toString())
|
||||
super.onReceivedSslError(view, handler, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
||||
|
||||
Reference in New Issue
Block a user