Calling UI camera toggle in PIP and in pre-join state.

This commit is contained in:
Alex Hart
2025-11-04 13:41:02 -04:00
committed by Michelle Tang
parent e6f11c7443
commit 89ba3a83ff
7 changed files with 327 additions and 245 deletions

View File

@@ -116,11 +116,11 @@ public class CallParticipantView extends ConstraintLayout {
useLargeAvatar();
}
void setMirror(boolean mirror) {
public void setMirror(boolean mirror) {
renderer.setMirror(mirror);
}
void setScalingType(@NonNull RendererCommon.ScalingType scalingType) {
public void setScalingType(@NonNull RendererCommon.ScalingType scalingType) {
renderer.setScalingType(scalingType);
}
@@ -128,7 +128,7 @@ public class CallParticipantView extends ConstraintLayout {
renderer.setScalingType(scalingTypeMatchOrientation, scalingTypeMismatchOrientation);
}
void setCallParticipant(@NonNull CallParticipant participant) {
public void setCallParticipant(@NonNull CallParticipant participant) {
boolean participantChanged = recipientId == null || !recipientId.equals(participant.getRecipient().getId());
recipientId = participant.getRecipient().getId();
infoMode = participant.getRecipient().isBlocked() || isMissingMediaKeys(participant);
@@ -221,7 +221,7 @@ public class CallParticipantView extends ConstraintLayout {
return false;
}
void setRenderInPip(boolean shouldRenderInPip) {
public void setRenderInPip(boolean shouldRenderInPip) {
this.shouldRenderInPip = shouldRenderInPip;
if (infoMode) {
@@ -243,11 +243,15 @@ public class CallParticipantView extends ConstraintLayout {
this.raiseHandAllowed = raiseHandAllowed;
}
public void setCameraToggleOnClickListener(@Nullable View.OnClickListener onClickListener) {
switchCameraIconFrame.setOnClickListener(onClickListener);
}
/**
* Adjust UI elements for the various self PIP positions. If called after a {@link TransitionManager#beginDelayedTransition(ViewGroup, Transition)},
* the changes to the UI elements will animate.
*/
void setSelfPipMode(@NonNull SelfPipMode selfPipMode, boolean isMoreThanOneCameraAvailable) {
public void setSelfPipMode(@NonNull SelfPipMode selfPipMode, boolean isMoreThanOneCameraAvailable) {
Preconditions.checkArgument(selfPipMode != SelfPipMode.NOT_SELF_PIP);
if (this.selfPipMode == selfPipMode) {
@@ -402,7 +406,7 @@ public class CallParticipantView extends ConstraintLayout {
ViewUtil.setBottomMargin(audioIndicator, desiredMargin);
}
void releaseRenderer() {
public void releaseRenderer() {
renderer.release();
}

View File

@@ -5,22 +5,19 @@
package org.thoughtcrime.securesms.components.webrtc.v2
import androidx.compose.foundation.layout.BoxWithConstraints
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.viewinterop.AndroidView
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
import org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
import org.thoughtcrime.securesms.compose.GlideImage
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.webrtc.CallParticipantView
import org.thoughtcrime.securesms.events.CallParticipant
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.ringrtc.CameraState
import org.webrtc.RendererCommon
@@ -30,59 +27,38 @@ import org.webrtc.RendererCommon
@Composable
fun CallParticipantRenderer(
callParticipant: CallParticipant,
renderInPip: Boolean,
modifier: Modifier = Modifier,
force: Boolean = false
isLocalParticipant: Boolean = false,
isRaiseHandAllowed: Boolean = false,
selfPipMode: CallParticipantView.SelfPipMode = CallParticipantView.SelfPipMode.NOT_SELF_PIP,
onToggleCameraDirection: () -> Unit = {}
) {
BoxWithConstraints(
modifier = modifier
) {
val maxWidth = constraints.maxWidth
val maxHeight = constraints.maxHeight
val density = LocalDensity.current
val size = with(density) {
DpSize(
width = maxWidth.toDp(),
height = maxHeight.toDp()
)
}
val localRecipient = if (LocalInspectionMode.current) {
Recipient()
} else {
callParticipant.recipient
}
val model = remember {
ProfileContactPhoto(localRecipient)
}
val context = LocalContext.current
val fallback = remember {
FallbackAvatarDrawable(context, localRecipient.getFallbackAvatar())
}
GlideImage(
model = model,
imageSize = size,
fallback = fallback,
modifier = Modifier.fillMaxSize()
)
if (force || callParticipant.isVideoEnabled) {
AndroidView(
factory = ::TextureViewRenderer,
modifier = Modifier.fillMaxSize(),
onRelease = { it.release() }
) { renderer ->
renderer.setMirror(callParticipant.cameraDirection == CameraState.Direction.FRONT)
renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
callParticipant.videoSink.lockableEglBase.performWithValidEglBase {
renderer.init(it)
if (LocalInspectionMode.current) {
Box(modifier.background(color = Color.Red))
} else {
AndroidView(
factory = { LayoutInflater.from(it).inflate(R.layout.call_participant_item, FrameLayout(it), false) as CallParticipantView },
modifier = modifier.fillMaxSize().background(color = Color.Red),
onRelease = { it.releaseRenderer() }
) { view ->
view.setCallParticipant(callParticipant)
view.setMirror(isLocalParticipant && callParticipant.cameraState.activeDirection == CameraState.Direction.FRONT)
view.setScalingType(
if (callParticipant.isScreenSharing && !isLocalParticipant) {
RendererCommon.ScalingType.SCALE_ASPECT_FIT
} else {
RendererCommon.ScalingType.SCALE_ASPECT_FILL
}
)
view.setRenderInPip(renderInPip)
view.setRaiseHandAllowed(isRaiseHandAllowed)
renderer.attachBroadcastVideoSink(callParticipant.videoSink)
if (selfPipMode != CallParticipantView.SelfPipMode.NOT_SELF_PIP) {
view.setSelfPipMode(selfPipMode, callParticipant.cameraState.cameraCount > 1)
view.setCameraToggleOnClickListener { onToggleCameraDirection() }
} else {
view.setCameraToggleOnClickListener(null)
}
}
}

View File

@@ -46,6 +46,7 @@ fun CallParticipantsPager(
1 -> {
CallParticipantRenderer(
callParticipant = callParticipantsPagerState.focusedParticipant,
renderInPip = callParticipantsPagerState.isRenderInPip,
modifier = Modifier.fillMaxSize()
)
}

View File

@@ -56,6 +56,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
@@ -73,12 +74,14 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.TriggerAlignedPopupState
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.webrtc.CallParticipantView
import org.thoughtcrime.securesms.components.webrtc.WebRtcLocalRenderState
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
import org.thoughtcrime.securesms.events.CallParticipant
import org.thoughtcrime.securesms.events.GroupCallReactionEvent
import org.thoughtcrime.securesms.events.WebRtcViewModel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.ringrtc.CameraState
import kotlin.math.max
import kotlin.math.round
import kotlin.time.Duration.Companion.seconds
@@ -170,7 +173,6 @@ fun CallScreen(
val maxSheetHeight = round(constraints.maxHeight * 0.66f)
val maxOffset = maxHeight - maxSheetHeight
var offset by remember { mutableFloatStateOf(0f) }
var peekHeight by remember { mutableFloatStateOf(88f) }
BottomSheetScaffold(
@@ -192,7 +194,7 @@ fun CallScreen(
.padding(top = SHEET_TOP_PADDING.dp, bottom = SHEET_BOTTOM_PADDING.dp)
.height(DimensionUnit.PIXELS.toDp(maxSheetHeight).dp)
.onGloballyPositioned {
offset = scaffoldState.bottomSheetState.requireOffset()
val offset = it.positionInRoot().y
val current = maxHeight - offset - DimensionUnit.DP.toPixels(peekHeight)
val maximum = maxHeight - maxOffset - DimensionUnit.DP.toPixels(peekHeight)
@@ -242,6 +244,7 @@ fun CallScreen(
onPipClick = onLocalPictureInPictureClicked,
onControlsToggled = onControlsToggled,
callScreenController = callScreenController,
onToggleCameraDirection = callScreenControlsListener::onCameraDirectionChanged,
modifier = if (isPortrait) {
Modifier.padding(bottom = padding)
} else Modifier
@@ -282,7 +285,9 @@ fun CallScreen(
callStatus = callScreenState.callStatus,
onNavigationClick = onNavigationClick,
onCallInfoClick = onCallInfoClick,
onCameraToggleClick = callScreenControlsListener::onCameraDirectionChanged,
isLocalVideoEnabled = localParticipant.isVideoEnabled,
isMoreThanOneCameraAvailable = localParticipant.isMoreThanOneCameraAvailable,
modifier = Modifier.padding(bottom = padding)
)
}
@@ -331,6 +336,7 @@ private fun BoxScope.Viewport(
callScreenController: CallScreenController,
onPipClick: () -> Unit,
onControlsToggled: (Boolean) -> Unit,
onToggleCameraDirection: () -> Unit,
modifier: Modifier = Modifier
) {
if (webRtcCallState.isPreJoinOrNetworkUnavailable) {
@@ -448,6 +454,7 @@ private fun BoxScope.Viewport(
localParticipant = localParticipant,
localRenderState = localRenderState,
onClick = onPipClick,
onToggleCameraDirection = onToggleCameraDirection,
modifier = modifier
)
}
@@ -463,6 +470,8 @@ private fun LargeLocalVideoRenderer(
) {
CallParticipantRenderer(
callParticipant = localParticipant,
isLocalParticipant = true,
renderInPip = false,
modifier = modifier
.fillMaxSize()
)
@@ -499,6 +508,9 @@ private fun TinyLocalVideoRenderer(
CallParticipantRenderer(
callParticipant = localParticipant,
isLocalParticipant = true,
renderInPip = true,
selfPipMode = CallParticipantView.SelfPipMode.MINI_SELF_PIP,
modifier = modifier
.padding(padding)
.height(height)
@@ -516,6 +528,7 @@ private fun SmallMoveableLocalVideoRenderer(
localParticipant: CallParticipant,
localRenderState: WebRtcLocalRenderState,
onClick: () -> Unit,
onToggleCameraDirection: () -> Unit,
modifier: Modifier = Modifier
) {
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
@@ -542,6 +555,14 @@ private fun SmallMoveableLocalVideoRenderer(
) {
CallParticipantRenderer(
callParticipant = localParticipant,
isLocalParticipant = true,
renderInPip = true,
selfPipMode = if (localRenderState == WebRtcLocalRenderState.EXPANDED) {
CallParticipantView.SelfPipMode.EXPANDED_SELF_PIP
} else {
CallParticipantView.SelfPipMode.NORMAL_SELF_PIP
},
onToggleCameraDirection = onToggleCameraDirection,
modifier = Modifier
.fillMaxSize()
.clip(MaterialTheme.shapes.medium)
@@ -631,7 +652,7 @@ private fun CallScreenPreview() {
Previews.Preview {
CallScreen(
callRecipient = Recipient(systemContactName = "Test User"),
webRtcCallState = WebRtcViewModel.State.CALL_CONNECTED,
webRtcCallState = WebRtcViewModel.State.CALL_PRE_JOIN,
isRemoteVideoOffer = false,
isInPipMode = false,
callScreenState = CallScreenState(
@@ -653,7 +674,11 @@ private fun CallScreenPreview() {
isResolving = false,
isSelf = true
),
isVideoEnabled = true
isVideoEnabled = true,
cameraState = CameraState(
CameraState.Direction.FRONT,
2
)
),
localRenderState = WebRtcLocalRenderState.SMALL_RECTANGLE,
callScreenDialogType = CallScreenDialogType.NONE,

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.webrtc.v2
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.NightPreview
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.AvatarImage
import org.thoughtcrime.securesms.recipients.Recipient
/**
* Pre-join call screen overlay.
*
* This overlay is displayed when the user has entered the calling screen but has not yet joined a call, and is
* positioned above the controls sheet.
*/
@Composable
fun CallScreenPreJoinOverlay(
callRecipient: Recipient,
callStatus: String?,
isMoreThanOneCameraAvailable: Boolean,
isLocalVideoEnabled: Boolean,
modifier: Modifier = Modifier,
onNavigationClick: () -> Unit = {},
onCallInfoClick: () -> Unit = {},
onCameraToggleClick: () -> Unit = {}
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxSize()
.background(color = Color(0f, 0f, 0f, 0.4f))
) {
CallScreenTopAppBar(
onNavigationClick = onNavigationClick,
onCallInfoClick = onCallInfoClick
)
AvatarImage(
recipient = callRecipient,
modifier = Modifier
.padding(top = 8.dp)
.size(96.dp)
)
Text(
text = callRecipient.getDisplayName(LocalContext.current),
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
modifier = Modifier.padding(top = 16.dp)
)
if (callStatus != null) {
Text(
text = callStatus,
style = MaterialTheme.typography.bodyMedium,
color = Color.White,
modifier = Modifier.padding(top = 8.dp)
)
}
if (!isLocalVideoEnabled) {
Spacer(modifier = Modifier.weight(1f))
Icon(
painter = painterResource(
id = R.drawable.symbol_video_slash_24
),
contentDescription = null,
tint = Color.White,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = stringResource(id = R.string.CallScreenPreJoinOverlay__your_camera_is_off),
color = Color.White
)
Spacer(modifier = Modifier.weight(1f))
}
if (isLocalVideoEnabled && isMoreThanOneCameraAvailable) {
Spacer(modifier = Modifier.weight(1f))
Box(modifier = Modifier.fillMaxWidth()) {
CallCameraDirectionToggle(
onClick = onCameraToggleClick,
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp)
)
}
}
}
}
@Composable
private fun CallCameraDirectionToggle(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(
onClick = onClick,
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = Color.White
),
shape = CircleShape,
modifier = modifier.size(48.dp)
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.symbol_switch_24),
contentDescription = stringResource(R.string.CallScreen__change_camera_direction)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CallScreenTopAppBar(
callRecipient: Recipient? = null,
callStatus: String? = null,
onNavigationClick: () -> Unit = {},
onCallInfoClick: () -> Unit = {},
modifier: Modifier = Modifier
) {
val textShadow = remember {
Shadow(
color = Color(0f, 0f, 0f, 0.25f),
blurRadius = 4f
)
}
TopAppBar(
modifier = modifier,
colors = TopAppBarDefaults.topAppBarColors().copy(
containerColor = Color.Transparent
),
title = {
Column {
if (callRecipient != null) {
Text(
text = callRecipient.getDisplayName(LocalContext.current),
style = MaterialTheme.typography.titleMedium.copy(shadow = textShadow)
)
}
if (callStatus != null) {
Text(
text = callStatus,
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow),
modifier = Modifier.padding(top = 2.dp)
)
}
}
},
navigationIcon = {
IconButton(
onClick = onNavigationClick
) {
Icon(
painter = painterResource(id = R.drawable.symbol_arrow_start_24),
contentDescription = stringResource(id = R.string.CallScreenTopBar__go_back),
tint = Color.White
)
}
},
actions = {
IconButton(
onClick = onCallInfoClick,
modifier = Modifier.padding(16.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_info_24),
contentDescription = stringResource(id = R.string.CallScreenTopBar__call_information),
tint = Color.White
)
}
}
)
}
@NightPreview
@Composable
fun CallScreenTopBarPreview() {
Previews.Preview {
CallScreenTopBar(
callRecipient = Recipient(systemContactName = "Test User"),
callStatus = null
)
}
}
@NightPreview
@Composable
fun CallScreenPreJoinOverlayPreview() {
Previews.Preview {
CallScreenPreJoinOverlay(
callRecipient = Recipient(systemContactName = "Test User"),
callStatus = stringResource(R.string.Recipient_unknown),
isLocalVideoEnabled = false,
isMoreThanOneCameraAvailable = false
)
}
}
@NightPreview
@Composable
fun CallScreenPreJoinOverlayWithTogglePreview() {
Previews.Preview {
CallScreenPreJoinOverlay(
callRecipient = Recipient(systemContactName = "Test User"),
callStatus = stringResource(R.string.Recipient_unknown),
isLocalVideoEnabled = true,
isMoreThanOneCameraAvailable = true
)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 Signal Messenger, LLC
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -7,34 +7,12 @@ package org.thoughtcrime.securesms.components.webrtc.v2
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.NightPreview
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.AvatarImage
import org.thoughtcrime.securesms.recipients.Recipient
/**
@@ -66,156 +44,3 @@ fun CallScreenTopBar(
)
}
}
@Composable
fun CallScreenPreJoinOverlay(
callRecipient: Recipient,
callStatus: String?,
isLocalVideoEnabled: Boolean,
modifier: Modifier = Modifier,
onNavigationClick: () -> Unit = {},
onCallInfoClick: () -> Unit = {}
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxSize()
.background(color = Color(0f, 0f, 0f, 0.4f))
) {
CallScreenTopAppBar(
onNavigationClick = onNavigationClick,
onCallInfoClick = onCallInfoClick
)
AvatarImage(
recipient = callRecipient,
modifier = Modifier
.padding(top = 8.dp)
.size(96.dp)
)
Text(
text = callRecipient.getDisplayName(LocalContext.current),
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
modifier = Modifier.padding(top = 16.dp)
)
if (callStatus != null) {
Text(
text = callStatus,
style = MaterialTheme.typography.bodyMedium,
color = Color.White,
modifier = Modifier.padding(top = 8.dp)
)
}
if (!isLocalVideoEnabled) {
Spacer(modifier = Modifier.weight(1f))
Icon(
painter = painterResource(
id = R.drawable.symbol_video_slash_24
),
contentDescription = null,
tint = Color.White,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = stringResource(id = R.string.CallScreenPreJoinOverlay__your_camera_is_off),
color = Color.White
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CallScreenTopAppBar(
callRecipient: Recipient? = null,
callStatus: String? = null,
onNavigationClick: () -> Unit = {},
onCallInfoClick: () -> Unit = {},
modifier: Modifier = Modifier
) {
val textShadow = remember {
Shadow(
color = Color(0f, 0f, 0f, 0.25f),
blurRadius = 4f
)
}
TopAppBar(
modifier = modifier,
colors = TopAppBarDefaults.topAppBarColors().copy(
containerColor = Color.Transparent
),
title = {
Column {
if (callRecipient != null) {
Text(
text = callRecipient.getDisplayName(LocalContext.current),
style = MaterialTheme.typography.titleMedium.copy(shadow = textShadow)
)
}
if (callStatus != null) {
Text(
text = callStatus,
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow),
modifier = Modifier.padding(top = 2.dp)
)
}
}
},
navigationIcon = {
IconButton(
onClick = onNavigationClick
) {
Icon(
painter = painterResource(id = R.drawable.symbol_arrow_start_24),
contentDescription = stringResource(id = R.string.CallScreenTopBar__go_back),
tint = Color.White
)
}
},
actions = {
IconButton(
onClick = onCallInfoClick,
modifier = Modifier.padding(16.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_info_24),
contentDescription = stringResource(id = R.string.CallScreenTopBar__call_information),
tint = Color.White
)
}
}
)
}
@NightPreview
@Composable
fun CallScreenTopBarPreview() {
Previews.Preview {
CallScreenTopBar(
callRecipient = Recipient(systemContactName = "Test User"),
callStatus = null
)
}
}
@NightPreview
@Composable
fun CallScreenPreJoinOverlayPreview() {
Previews.Preview {
CallScreenPreJoinOverlay(
callRecipient = Recipient(systemContactName = "Test User"),
callStatus = stringResource(R.string.Recipient_unknown),
isLocalVideoEnabled = false
)
}
}

View File

@@ -3373,6 +3373,9 @@
<!-- Label with hyphenation. Translation can use soft hyphen - Unicode U+00AD -->
<string name="WebRtcCallScreen__answer_without_video">Answer without video</string>
<!-- CallScreen -->
<string name="CallScreen__change_camera_direction">Change camera direction</string>
<!-- WebRtcAudioOutputToggle -->
<!-- Label for a dialog asking the user to switch the audio output device during a call -->
<string name="WebRtcAudioOutputToggle__audio_output">Audio output</string>