diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java
index bc75c45973..c4fed216a5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java
@@ -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();
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantRenderer.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantRenderer.kt
index f3399d484c..cff7f5e86d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantRenderer.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantRenderer.kt
@@ -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)
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt
index acdbd4a8c0..7ac0d46e4a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt
@@ -46,6 +46,7 @@ fun CallParticipantsPager(
1 -> {
CallParticipantRenderer(
callParticipant = callParticipantsPagerState.focusedParticipant,
+ renderInPip = callParticipantsPagerState.isRenderInPip,
modifier = Modifier.fillMaxSize()
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt
index f8f7e19212..eaf76c94ed 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt
@@ -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,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenPreJoinOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenPreJoinOverlay.kt
new file mode 100644
index 0000000000..206bfd5dab
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenPreJoinOverlay.kt
@@ -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
+ )
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenTopBar.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenTopBar.kt
index 85cad50bb5..8581cb03da 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenTopBar.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreenTopBar.kt
@@ -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
- )
- }
-}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0212bf2cd2..cfeaea6b05 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3373,6 +3373,9 @@
Answer without video
+
+ Change camera direction
+
Audio output