Element Call: remove top app bar and add it inside the webview instead (#4927)
Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
c13865b275
commit
3c9b2144cd
16 changed files with 33 additions and 153 deletions
|
|
@ -7,16 +7,6 @@
|
||||||
|
|
||||||
package io.element.android.features.call.impl.pip
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
|
|
||||||
open class PictureInPictureStateProvider : PreviewParameterProvider<PictureInPictureState> {
|
|
||||||
override val values: Sequence<PictureInPictureState>
|
|
||||||
get() = sequenceOf(
|
|
||||||
aPictureInPictureState(supportPip = true),
|
|
||||||
aPictureInPictureState(supportPip = true, isInPictureInPicture = true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun aPictureInPictureState(
|
fun aPictureInPictureState(
|
||||||
supportPip: Boolean = false,
|
supportPip: Boolean = false,
|
||||||
isInPictureInPicture: Boolean = false,
|
isInPictureInPicture: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.ConsoleMessage
|
import android.webkit.ConsoleMessage
|
||||||
|
import android.webkit.JavascriptInterface
|
||||||
import android.webkit.PermissionRequest
|
import android.webkit.PermissionRequest
|
||||||
import android.webkit.WebChromeClient
|
import android.webkit.WebChromeClient
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
|
@ -19,7 +20,6 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -32,11 +32,9 @@ import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.res.stringResource
|
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.compound.tokens.generated.CompoundIcons
|
|
||||||
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.PictureInPictureEvents
|
||||||
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.PictureInPictureStateProvider
|
|
||||||
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
|
||||||
import io.element.android.features.call.impl.utils.WebViewAudioManager
|
import io.element.android.features.call.impl.utils.WebViewAudioManager
|
||||||
|
|
@ -44,13 +42,11 @@ import io.element.android.features.call.impl.utils.WebViewPipController
|
||||||
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
|
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -60,7 +56,6 @@ interface CallScreenNavigator {
|
||||||
fun close()
|
fun close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CallScreenView(
|
internal fun CallScreenView(
|
||||||
state: CallScreenState,
|
state: CallScreenState,
|
||||||
|
|
@ -78,19 +73,6 @@ internal fun CallScreenView(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
|
||||||
if (!pipState.isInPictureInPicture) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(stringResource(R.string.element_call)) },
|
|
||||||
navigationIcon = {
|
|
||||||
BackButton(
|
|
||||||
imageVector = if (pipState.supportPip) CompoundIcons.ArrowLeft() else CompoundIcons.Close(),
|
|
||||||
onClick = ::handleBack,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { padding ->
|
) { padding ->
|
||||||
BackHandler {
|
BackHandler {
|
||||||
handleBack()
|
handleBack()
|
||||||
|
|
@ -127,9 +109,11 @@ internal fun CallScreenView(
|
||||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||||
},
|
},
|
||||||
onCreateWebView = { webView ->
|
onCreateWebView = { webView ->
|
||||||
|
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||||
val interceptor = WebViewWidgetMessageInterceptor(
|
val interceptor = WebViewWidgetMessageInterceptor(
|
||||||
webView = webView,
|
webView = webView,
|
||||||
onUrlLoaded = { url ->
|
onUrlLoaded = { url ->
|
||||||
|
webView.evaluateJavascript("controls.onBackButtonPressed = () => { backHandler.onBackPressed() }", null)
|
||||||
if (webViewAudioManager?.isInCallMode?.get() == false) {
|
if (webViewAudioManager?.isInCallMode?.get() == false) {
|
||||||
Timber.d("URL $url is loaded, starting in-call audio mode")
|
Timber.d("URL $url is loaded, starting in-call audio mode")
|
||||||
webViewAudioManager?.onCallStarted()
|
webViewAudioManager?.onCallStarted()
|
||||||
|
|
@ -282,6 +266,17 @@ private fun WebView.setup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun WebView.addBackHandler(onBackPressed: () -> Unit) {
|
||||||
|
addJavascriptInterface(
|
||||||
|
object {
|
||||||
|
@Suppress("unused")
|
||||||
|
@JavascriptInterface
|
||||||
|
fun onBackPressed() = onBackPressed()
|
||||||
|
},
|
||||||
|
"backHandler"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CallScreenViewPreview(
|
internal fun CallScreenViewPreview(
|
||||||
|
|
@ -294,18 +289,6 @@ internal fun CallScreenViewPreview(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun CallScreenPipViewPreview(
|
|
||||||
@PreviewParameter(PictureInPictureStateProvider::class) state: PictureInPictureState,
|
|
||||||
) = ElementPreview {
|
|
||||||
CallScreenView(
|
|
||||||
state = aCallScreenState(),
|
|
||||||
pipState = state,
|
|
||||||
requestPermissions = { _, _ -> },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun InvalidAudioDeviceDialogPreview() = ElementPreview {
|
internal fun InvalidAudioDeviceDialogPreview() = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.call.impl.ui
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
|
||||||
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
|
||||||
import io.element.android.tests.testutils.pressBack
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class CallScreenViewTest {
|
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `clicking on back when pip is not supported hangs up`() {
|
|
||||||
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
|
||||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>()
|
|
||||||
rule.setCallScreenView(
|
|
||||||
aCallScreenState(
|
|
||||||
eventSink = eventsRecorder
|
|
||||||
),
|
|
||||||
aPictureInPictureState(
|
|
||||||
supportPip = false,
|
|
||||||
eventSink = pipEventsRecorder,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
rule.pressBack()
|
|
||||||
eventsRecorder.assertSize(2)
|
|
||||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
|
||||||
eventsRecorder.assertTrue(1) { it == CallScreenEvents.Hangup }
|
|
||||||
pipEventsRecorder.assertSize(1)
|
|
||||||
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `clicking on back when pip is supported enables PiP`() {
|
|
||||||
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
|
||||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>()
|
|
||||||
rule.setCallScreenView(
|
|
||||||
aCallScreenState(
|
|
||||||
eventSink = eventsRecorder
|
|
||||||
),
|
|
||||||
aPictureInPictureState(
|
|
||||||
supportPip = true,
|
|
||||||
eventSink = pipEventsRecorder,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
rule.pressBack()
|
|
||||||
eventsRecorder.assertSize(1)
|
|
||||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
|
||||||
pipEventsRecorder.assertSize(2)
|
|
||||||
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
|
||||||
pipEventsRecorder.assertTrue(1) { it == PictureInPictureEvents.EnterPictureInPicture }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCallScreenView(
|
|
||||||
state: CallScreenState,
|
|
||||||
pipState: PictureInPictureState,
|
|
||||||
requestPermissions: (Array<String>, RequestPermissionCallback) -> Unit = { _, _ -> },
|
|
||||||
) {
|
|
||||||
setContent {
|
|
||||||
CallScreenView(
|
|
||||||
state = state,
|
|
||||||
pipState = pipState,
|
|
||||||
requestPermissions = requestPermissions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,6 +18,7 @@ import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import org.matrix.rustcomponents.sdk.newVirtualElementCallWidget
|
import org.matrix.rustcomponents.sdk.newVirtualElementCallWidget
|
||||||
import uniffi.matrix_sdk.EncryptionSystem
|
import uniffi.matrix_sdk.EncryptionSystem
|
||||||
|
import uniffi.matrix_sdk.HeaderStyle
|
||||||
import uniffi.matrix_sdk.VirtualElementCallWidgetOptions
|
import uniffi.matrix_sdk.VirtualElementCallWidgetOptions
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import uniffi.matrix_sdk.Intent as CallIntent
|
import uniffi.matrix_sdk.Intent as CallIntent
|
||||||
|
|
@ -48,9 +49,10 @@ class DefaultCallWidgetSettingsProvider @Inject constructor(
|
||||||
sentryDsn = callAnalyticsCredentialsProvider.sentryDsn.takeIf { isAnalyticsEnabled },
|
sentryDsn = callAnalyticsCredentialsProvider.sentryDsn.takeIf { isAnalyticsEnabled },
|
||||||
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) "RELEASE" else "DEBUG",
|
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) "RELEASE" else "DEBUG",
|
||||||
parentUrl = null,
|
parentUrl = null,
|
||||||
|
// For backwards compatibility, it'll be ignored in recent versions of Element Call
|
||||||
hideHeader = true,
|
hideHeader = true,
|
||||||
controlledMediaDevices = true,
|
controlledMediaDevices = true,
|
||||||
header = null,
|
header = HeaderStyle.APP_BAR,
|
||||||
)
|
)
|
||||||
val rustWidgetSettings = newVirtualElementCallWidget(options)
|
val rustWidgetSettings = newVirtualElementCallWidget(options)
|
||||||
return MatrixWidgetSettings.fromRustWidgetSettings(rustWidgetSettings)
|
return MatrixWidgetSettings.fromRustWidgetSettings(rustWidgetSettings)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:b61b391f6380189603939badc2ae5912f5cb072200a001ff946af2d52e81b95a
|
|
||||||
size 12734
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:7696eb0f5bf8698d001ea44be6eb2005f414057f9a65703e6d987e8eb75f7f94
|
|
||||||
size 9441
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:c3d14805d85b33ce1f0fdd26eec433db1973924542cd85a3bb0e4514cdcd857d
|
|
||||||
size 12392
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:146fc2ce4d344c0ea947fe0370e6b8d94c2e724c69c01c2cc3476a756e1f09e4
|
|
||||||
size 9315
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8cc555b1614f3d9e5181769ec203da84f0fb01bbec9b7edf1ad53944158aea7e
|
oid sha256:7696eb0f5bf8698d001ea44be6eb2005f414057f9a65703e6d987e8eb75f7f94
|
||||||
size 12824
|
size 9441
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5480b1c7a3244e0c702f6623f736bfc76560843a8eba8c118a1560954414b4f4
|
oid sha256:5d22e3789c7515c5fcb0ee0c72a7c238947f526b6959e1590a29008c8cfa9919
|
||||||
size 14560
|
size 11737
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8b42b1ad21ab76ffb7b555809e5d5d4495a6b0faceba181735369e0635ed0a56
|
oid sha256:b4d802d233fdb12dbbb8136d8b127b10898ba7c5963ebbe2f8ce4d015016b984
|
||||||
size 13847
|
size 10892
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:73bbf4bfe808722a4d2444ab78bd1f6ab6978d0e427dfaed0d0b957d153a00e5
|
oid sha256:6d885da406fe9866a5390a65bb6f8dda3e2242bdec4ac59e0ec654a16838bbf6
|
||||||
size 19294
|
size 16309
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:037a5e384eef63ade207a6a938409897f11efc83f1cbb1d382474bf21dbb54c2
|
oid sha256:146fc2ce4d344c0ea947fe0370e6b8d94c2e724c69c01c2cc3476a756e1f09e4
|
||||||
size 12524
|
size 9315
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:548c1d91bfee3a9fe22e881d61c469369885f51f80e267ce9e16c6f4501948fe
|
oid sha256:8f2d4611b2fc7156c855b13d8b390bc9be80b877407f521a8d196434e97650fe
|
||||||
size 13372
|
size 10519
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5cd1c4e6317f6372c3c341f7e681d82533038a15c2287f584603330b3cf62a40
|
oid sha256:b58875c87cfdf54fcfd6990cc4c7637d74d85faed1c69bc4e5c671afb827ea4d
|
||||||
size 12316
|
size 9483
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6de376c3fa6098ba16522181a7b7835660c57c77bfd2c7d8ca75d372c4144676
|
oid sha256:928fecaa9000e8c0309d4bbbd7742921ec83497cb65868ffda1a2faf0cba9b57
|
||||||
size 17437
|
size 14599
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue