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
27a5787b13
commit
2f6ce3da33
16 changed files with 33 additions and 153 deletions
|
|
@ -7,16 +7,6 @@
|
|||
|
||||
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(
|
||||
supportPip: Boolean = false,
|
||||
isInPictureInPicture: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.annotation.SuppressLint
|
|||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.PermissionRequest
|
||||
import android.webkit.WebChromeClient
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
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.tooling.preview.PreviewParameter
|
||||
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.pip.PictureInPictureEvents
|
||||
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.utils.InvalidAudioDeviceReason
|
||||
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.libraries.architecture.AsyncData
|
||||
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.preview.ElementPreview
|
||||
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.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -60,7 +56,6 @@ interface CallScreenNavigator {
|
|||
fun close()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun CallScreenView(
|
||||
state: CallScreenState,
|
||||
|
|
@ -78,19 +73,6 @@ internal fun CallScreenView(
|
|||
|
||||
Scaffold(
|
||||
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 ->
|
||||
BackHandler {
|
||||
handleBack()
|
||||
|
|
@ -127,9 +109,11 @@ internal fun CallScreenView(
|
|||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onCreateWebView = { webView ->
|
||||
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
webView = webView,
|
||||
onUrlLoaded = { url ->
|
||||
webView.evaluateJavascript("controls.onBackButtonPressed = () => { backHandler.onBackPressed() }", null)
|
||||
if (webViewAudioManager?.isInCallMode?.get() == false) {
|
||||
Timber.d("URL $url is loaded, starting in-call audio mode")
|
||||
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
|
||||
@Composable
|
||||
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
|
||||
@Composable
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue