mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-22 12:08:34 +00:00
Implement the incoming call screen in compose.
This commit is contained in:
@@ -21,6 +21,7 @@ import androidx.compose.ui.res.colorResource
|
|||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.signal.core.ui.Buttons
|
import org.signal.core.ui.Buttons
|
||||||
import org.signal.core.ui.DarkPreview
|
import org.signal.core.ui.DarkPreview
|
||||||
@@ -28,6 +29,8 @@ import org.signal.core.ui.IconButtons
|
|||||||
import org.signal.core.ui.Previews
|
import org.signal.core.ui.Previews
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
|
||||||
|
private val defaultCallButtonIconSize: Dp = 24.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ToggleCallButton(
|
private fun ToggleCallButton(
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
@@ -67,7 +70,8 @@ private fun CallButton(
|
|||||||
contentDescription: String?,
|
contentDescription: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
containerColor: Color = MaterialTheme.colorScheme.secondaryContainer,
|
containerColor: Color = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
contentColor: Color = colorResource(id = R.color.signal_light_colorOnPrimary)
|
contentColor: Color = colorResource(id = R.color.signal_light_colorOnPrimary),
|
||||||
|
iconSize: Dp = defaultCallButtonIconSize
|
||||||
) {
|
) {
|
||||||
val buttonSize = dimensionResource(id = R.dimen.webrtc_button_size)
|
val buttonSize = dimensionResource(id = R.dimen.webrtc_button_size)
|
||||||
IconButtons.IconButton(
|
IconButtons.IconButton(
|
||||||
@@ -82,7 +86,8 @@ private fun CallButton(
|
|||||||
Icon(
|
Icon(
|
||||||
painter = painter,
|
painter = painter,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = Modifier.size(28.dp)
|
modifier = Modifier.size(iconSize),
|
||||||
|
tint = contentColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,13 +157,51 @@ fun AdditionalActionsButton(
|
|||||||
@Composable
|
@Composable
|
||||||
fun HangupButton(
|
fun HangupButton(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
iconSize: Dp = defaultCallButtonIconSize
|
||||||
) {
|
) {
|
||||||
CallButton(
|
CallButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
painter = painterResource(id = R.drawable.symbol_phone_down_fill_24),
|
painter = painterResource(id = R.drawable.symbol_phone_down_fill_24),
|
||||||
contentDescription = stringResource(id = R.string.WebRtcCallView__end_call),
|
contentDescription = stringResource(id = R.string.WebRtcCallView__end_call),
|
||||||
containerColor = colorResource(id = R.color.webrtc_hangup_background),
|
containerColor = colorResource(id = R.color.webrtc_hangup_background),
|
||||||
|
modifier = modifier,
|
||||||
|
iconSize = iconSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AcceptCallButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
isVideoCall: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
iconSize: Dp = defaultCallButtonIconSize
|
||||||
|
) {
|
||||||
|
CallButton(
|
||||||
|
onClick = onClick,
|
||||||
|
painter = if (isVideoCall) {
|
||||||
|
painterResource(id = R.drawable.symbol_video_fill_24)
|
||||||
|
} else {
|
||||||
|
painterResource(id = R.drawable.symbol_phone_fill_white_24)
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(id = R.string.WebRtcCallScreen__answer),
|
||||||
|
containerColor = colorResource(id = R.color.webrtc_answer_background),
|
||||||
|
iconSize = iconSize,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AnswerWithoutVideoButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
CallButton(
|
||||||
|
onClick = onClick,
|
||||||
|
painter = painterResource(id = R.drawable.symbol_video_slash_fill_24),
|
||||||
|
contentDescription = stringResource(id = R.string.WebRtcCallScreen__answer_without_video),
|
||||||
|
containerColor = Color.White,
|
||||||
|
contentColor = Color.Black,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -261,6 +304,38 @@ private fun HangupButtonPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DarkPreview
|
||||||
|
@Composable
|
||||||
|
private fun VideoAcceptCallButtonPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
AcceptCallButton(
|
||||||
|
onClick = {},
|
||||||
|
isVideoCall = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DarkPreview
|
||||||
|
@Composable
|
||||||
|
private fun AcceptCallButtonPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
AcceptCallButton(
|
||||||
|
onClick = {},
|
||||||
|
isVideoCall = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DarkPreview
|
||||||
|
@Composable
|
||||||
|
private fun AnswerWithoutVideoButtonPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
AnswerWithoutVideoButton(
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@DarkPreview
|
@DarkPreview
|
||||||
@Composable
|
@Composable
|
||||||
private fun StartCallButtonPreview() {
|
private fun StartCallButtonPreview() {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ private const val SHEET_BOTTOM_PADDING = 16
|
|||||||
fun CallScreen(
|
fun CallScreen(
|
||||||
callRecipient: Recipient,
|
callRecipient: Recipient,
|
||||||
webRtcCallState: WebRtcViewModel.State,
|
webRtcCallState: WebRtcViewModel.State,
|
||||||
|
isRemoteVideoOffer: Boolean,
|
||||||
callScreenState: CallScreenState,
|
callScreenState: CallScreenState,
|
||||||
callControlsState: CallControlsState,
|
callControlsState: CallControlsState,
|
||||||
callScreenController: CallScreenController = CallScreenController.rememberCallScreenController(
|
callScreenController: CallScreenController = CallScreenController.rememberCallScreenController(
|
||||||
@@ -98,6 +99,16 @@ fun CallScreen(
|
|||||||
onControlsToggled: (Boolean) -> Unit,
|
onControlsToggled: (Boolean) -> Unit,
|
||||||
onCallScreenDialogDismissed: () -> Unit = {}
|
onCallScreenDialogDismissed: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
if (webRtcCallState == WebRtcViewModel.State.CALL_INCOMING) {
|
||||||
|
IncomingCallScreen(
|
||||||
|
callRecipient = callRecipient,
|
||||||
|
isVideoCall = isRemoteVideoOffer,
|
||||||
|
callStatus = callScreenState.callStatus,
|
||||||
|
callScreenControlsListener = callScreenControlsListener
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var peekPercentage by remember {
|
var peekPercentage by remember {
|
||||||
mutableFloatStateOf(0f)
|
mutableFloatStateOf(0f)
|
||||||
}
|
}
|
||||||
@@ -284,8 +295,7 @@ private fun BoxScope.Viewport(
|
|||||||
) {
|
) {
|
||||||
if (webRtcCallState.isPreJoinOrNetworkUnavailable) {
|
if (webRtcCallState.isPreJoinOrNetworkUnavailable) {
|
||||||
LargeLocalVideoRenderer(
|
LargeLocalVideoRenderer(
|
||||||
localParticipant = localParticipant,
|
localParticipant = localParticipant
|
||||||
localRenderState = localRenderState
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,12 +379,10 @@ private fun BoxScope.Viewport(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun LargeLocalVideoRenderer(
|
private fun LargeLocalVideoRenderer(
|
||||||
localParticipant: CallParticipant,
|
localParticipant: CallParticipant
|
||||||
localRenderState: WebRtcLocalRenderState
|
|
||||||
) {
|
) {
|
||||||
LocalParticipantRenderer(
|
LocalParticipantRenderer(
|
||||||
localParticipant = localParticipant,
|
localParticipant = localParticipant,
|
||||||
localRenderState = localRenderState,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.extraLarge)
|
.clip(MaterialTheme.shapes.extraLarge)
|
||||||
@@ -407,7 +415,6 @@ private fun TinyLocalVideoRenderer(
|
|||||||
|
|
||||||
LocalParticipantRenderer(
|
LocalParticipantRenderer(
|
||||||
localParticipant = localParticipant,
|
localParticipant = localParticipant,
|
||||||
localRenderState = localRenderState,
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.height(height)
|
.height(height)
|
||||||
@@ -449,7 +456,6 @@ private fun SmallMoveableLocalVideoRenderer(
|
|||||||
) {
|
) {
|
||||||
LocalParticipantRenderer(
|
LocalParticipantRenderer(
|
||||||
localParticipant = localParticipant,
|
localParticipant = localParticipant,
|
||||||
localRenderState = localRenderState,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
@@ -498,6 +504,7 @@ private fun CallScreenPreview() {
|
|||||||
CallScreen(
|
CallScreen(
|
||||||
callRecipient = Recipient(systemContactName = "Test User"),
|
callRecipient = Recipient(systemContactName = "Test User"),
|
||||||
webRtcCallState = WebRtcViewModel.State.CALL_CONNECTED,
|
webRtcCallState = WebRtcViewModel.State.CALL_CONNECTED,
|
||||||
|
isRemoteVideoOffer = false,
|
||||||
callScreenState = CallScreenState(),
|
callScreenState = CallScreenState(),
|
||||||
callControlsState = CallControlsState(
|
callControlsState = CallControlsState(
|
||||||
displayMicToggle = true,
|
displayMicToggle = true,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ interface CallScreenMediator {
|
|||||||
fun enableParticipantUpdatePopup(enabled: Boolean)
|
fun enableParticipantUpdatePopup(enabled: Boolean)
|
||||||
fun enableCallStateUpdatePopup(enabled: Boolean)
|
fun enableCallStateUpdatePopup(enabled: Boolean)
|
||||||
fun showWifiToCellularPopupWindow()
|
fun showWifiToCellularPopupWindow()
|
||||||
|
fun hideMissingPermissionsNotice()
|
||||||
|
|
||||||
fun setStatusFromGroupCallState(context: Context, groupCallState: WebRtcViewModel.GroupCallState) {
|
fun setStatusFromGroupCallState(context: Context, groupCallState: WebRtcViewModel.GroupCallState) {
|
||||||
when (groupCallState) {
|
when (groupCallState) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ data class CallScreenState(
|
|||||||
val displaySwipeToSpeakerHint: Boolean = false,
|
val displaySwipeToSpeakerHint: Boolean = false,
|
||||||
val displayWifiToCellularPopup: Boolean = false,
|
val displayWifiToCellularPopup: Boolean = false,
|
||||||
val displayAdditionalActionsPopup: Boolean = false,
|
val displayAdditionalActionsPopup: Boolean = false,
|
||||||
|
val displayMissingPermissionsNotice: Boolean = false,
|
||||||
val pendingParticipantsState: PendingParticipantsState? = null,
|
val pendingParticipantsState: PendingParticipantsState? = null,
|
||||||
val isParticipantUpdatePopupEnabled: Boolean = false,
|
val isParticipantUpdatePopupEnabled: Boolean = false,
|
||||||
val isCallStateUpdatePopupEnabled: Boolean = false
|
val isCallStateUpdatePopupEnabled: Boolean = false
|
||||||
|
|||||||
@@ -134,11 +134,12 @@ fun CallScreenPreJoinOverlay(
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallScreenTopAppBar(
|
fun CallScreenTopAppBar(
|
||||||
callRecipient: Recipient? = null,
|
callRecipient: Recipient? = null,
|
||||||
callStatus: String? = null,
|
callStatus: String? = null,
|
||||||
onNavigationClick: () -> Unit = {},
|
onNavigationClick: () -> Unit = {},
|
||||||
onCallInfoClick: () -> Unit = {}
|
onCallInfoClick: () -> Unit = {},
|
||||||
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val textShadow = remember {
|
val textShadow = remember {
|
||||||
Shadow(
|
Shadow(
|
||||||
@@ -148,6 +149,7 @@ private fun CallScreenTopAppBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
modifier = modifier,
|
||||||
colors = TopAppBarDefaults.topAppBarColors().copy(
|
colors = TopAppBarDefaults.topAppBarColors().copy(
|
||||||
containerColor = Color.Transparent
|
containerColor = Color.Transparent
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class ComposeCallScreenMediator(activity: WebRtcCallActivity, viewModel: WebRtcC
|
|||||||
CallScreen(
|
CallScreen(
|
||||||
callRecipient = recipient,
|
callRecipient = recipient,
|
||||||
webRtcCallState = webRtcCallState,
|
webRtcCallState = webRtcCallState,
|
||||||
|
isRemoteVideoOffer = viewModel.isAnswerWithVideoAvailable(),
|
||||||
callScreenState = callScreenState,
|
callScreenState = callScreenState,
|
||||||
callControlsState = callControlsState,
|
callControlsState = callControlsState,
|
||||||
callScreenController = callScreenController,
|
callScreenController = callScreenController,
|
||||||
@@ -276,6 +277,10 @@ class ComposeCallScreenMediator(activity: WebRtcCallActivity, viewModel: WebRtcC
|
|||||||
callScreenViewModel.callScreenState.update { it.copy(displayWifiToCellularPopup = true) }
|
callScreenViewModel.callScreenState.update { it.copy(displayWifiToCellularPopup = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hideMissingPermissionsNotice() {
|
||||||
|
callScreenViewModel.callScreenState.update { it.copy(displayMissingPermissionsNotice = false) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State holder for compose call screen
|
* State holder for compose call screen
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.BlurredEdgeTreatment
|
||||||
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shadow
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.DarkPreview
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.avatar.AvatarImage
|
||||||
|
import org.thoughtcrime.securesms.compose.GlideImage
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
|
private val textShadow = Shadow(
|
||||||
|
color = Color(0f, 0f, 0f, 0.25f),
|
||||||
|
blurRadius = 4f
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IncomingCallScreen(
|
||||||
|
callRecipient: Recipient,
|
||||||
|
callStatus: String?,
|
||||||
|
isVideoCall: Boolean,
|
||||||
|
callScreenControlsListener: CallScreenControlsListener
|
||||||
|
) {
|
||||||
|
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
val callTypePadding = remember(isLandscape) {
|
||||||
|
if (isLandscape) {
|
||||||
|
PaddingValues(top = 0.dp, bottom = 20.dp)
|
||||||
|
} else {
|
||||||
|
PaddingValues(top = 22.dp, bottom = 30.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold { contentPadding ->
|
||||||
|
|
||||||
|
GlideImage(
|
||||||
|
model = callRecipient.contactPhoto,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
.blur(
|
||||||
|
radiusX = 25.dp,
|
||||||
|
radiusY = 25.dp,
|
||||||
|
edgeTreatment = BlurredEdgeTreatment.Rectangle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = Color.Black.copy(alpha = 0.4f))
|
||||||
|
) {}
|
||||||
|
|
||||||
|
CallScreenTopAppBar(
|
||||||
|
callRecipient = null,
|
||||||
|
callStatus = null,
|
||||||
|
onNavigationClick = callScreenControlsListener::onNavigateUpClicked,
|
||||||
|
onCallInfoClick = callScreenControlsListener::onCallInfoClicked,
|
||||||
|
modifier = Modifier.padding(contentPadding)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(contentPadding)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(callTypePadding)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_signal_logo_small),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(end = 6.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = if (isVideoCall) {
|
||||||
|
stringResource(R.string.WebRtcCallView__signal_video_call)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.WebRtcCallView__signal_call)
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarImage(
|
||||||
|
recipient = callRecipient,
|
||||||
|
modifier = Modifier.size(80.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = callRecipient.getDisplayName(LocalContext.current),
|
||||||
|
style = if (isLandscape) {
|
||||||
|
MaterialTheme.typography.titleLarge.copy(shadow = textShadow)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.typography.headlineMedium.copy(shadow = textShadow)
|
||||||
|
},
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (callStatus != null) {
|
||||||
|
Text(
|
||||||
|
text = callStatus,
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow),
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
if (isLandscape) {
|
||||||
|
LandscapeButtons(isVideoCall, callScreenControlsListener)
|
||||||
|
} else {
|
||||||
|
PortraitButtons(isVideoCall, callScreenControlsListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PortraitButtons(
|
||||||
|
isVideoCall: Boolean,
|
||||||
|
callScreenControlsListener: CallScreenControlsListener
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = spacedBy(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 64.dp)
|
||||||
|
.padding(bottom = 24.dp)
|
||||||
|
) {
|
||||||
|
if (isVideoCall) {
|
||||||
|
AnswerWithoutVideoButtonAndLabel(
|
||||||
|
onClick = callScreenControlsListener::onAcceptCallWithVoiceOnlyPressed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
DeclineButtonAndLabel(
|
||||||
|
onClick = callScreenControlsListener::onDenyCallPressed
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
AnswerCallButtonAndLabel(
|
||||||
|
isVideoCall = isVideoCall,
|
||||||
|
onClick = callScreenControlsListener::onAcceptCallPressed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LandscapeButtons(
|
||||||
|
isVideoCall: Boolean,
|
||||||
|
callScreenControlsListener: CallScreenControlsListener
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(20.dp),
|
||||||
|
horizontalArrangement = spacedBy(45.dp)
|
||||||
|
) {
|
||||||
|
DeclineButtonAndLabel(
|
||||||
|
onClick = callScreenControlsListener::onDenyCallPressed
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isVideoCall) {
|
||||||
|
AnswerWithoutVideoButtonAndLabel(
|
||||||
|
onClick = callScreenControlsListener::onAcceptCallWithVoiceOnlyPressed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnswerCallButtonAndLabel(
|
||||||
|
isVideoCall = isVideoCall,
|
||||||
|
onClick = callScreenControlsListener::onAcceptCallPressed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DeclineButtonAndLabel(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
HangupButton(
|
||||||
|
onClick = onClick,
|
||||||
|
iconSize = 32.dp,
|
||||||
|
modifier = Modifier.size(78.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.WebRtcCallScreen__decline),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AnswerWithoutVideoButtonAndLabel(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(78.dp)
|
||||||
|
) {
|
||||||
|
AnswerWithoutVideoButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(56.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.WebRtcCallScreen__answer_without_video),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AnswerCallButtonAndLabel(
|
||||||
|
isVideoCall: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
AcceptCallButton(
|
||||||
|
isVideoCall = isVideoCall,
|
||||||
|
onClick = onClick,
|
||||||
|
iconSize = 32.dp,
|
||||||
|
modifier = Modifier.size(78.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.WebRtcCallScreen__answer),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(shadow = textShadow)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DarkPreview
|
||||||
|
@Preview(device = "spec:parent=pixel_5,orientation=landscape", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun IncomingVideoCallScreenPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
IncomingCallScreen(
|
||||||
|
callRecipient = Recipient(
|
||||||
|
systemContactName = "Test User"
|
||||||
|
),
|
||||||
|
callScreenControlsListener = CallScreenControlsListener.Empty,
|
||||||
|
isVideoCall = true,
|
||||||
|
callStatus = "Spiderman is calling the group"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DarkPreview
|
||||||
|
@Preview(device = "spec:parent=pixel_5,orientation=landscape", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun IncomingAudioCallScreenPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
IncomingCallScreen(
|
||||||
|
callRecipient = Recipient(
|
||||||
|
systemContactName = "Test User"
|
||||||
|
),
|
||||||
|
callScreenControlsListener = CallScreenControlsListener.Empty,
|
||||||
|
isVideoCall = false,
|
||||||
|
callStatus = "Spiderman is calling the group"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
|
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
|
||||||
import org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
|
import org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcLocalRenderState
|
|
||||||
import org.thoughtcrime.securesms.compose.GlideImage
|
import org.thoughtcrime.securesms.compose.GlideImage
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
|
||||||
import org.thoughtcrime.securesms.events.CallParticipant
|
import org.thoughtcrime.securesms.events.CallParticipant
|
||||||
@@ -31,8 +30,8 @@ import org.webrtc.RendererCommon
|
|||||||
@Composable
|
@Composable
|
||||||
fun LocalParticipantRenderer(
|
fun LocalParticipantRenderer(
|
||||||
localParticipant: CallParticipant,
|
localParticipant: CallParticipant,
|
||||||
localRenderState: WebRtcLocalRenderState,
|
modifier: Modifier = Modifier,
|
||||||
modifier: Modifier = Modifier
|
force: Boolean = false
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(
|
BoxWithConstraints(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -70,7 +69,7 @@ fun LocalParticipantRenderer(
|
|||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
if (localParticipant.isVideoEnabled) {
|
if (force || localParticipant.isVideoEnabled) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = ::TextureViewRenderer,
|
factory = ::TextureViewRenderer,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.thoughtcrime.securesms.components.webrtc.v2
|
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||||
@@ -22,6 +23,7 @@ import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoCont
|
|||||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel
|
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel
|
import org.thoughtcrime.securesms.events.WebRtcViewModel
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.util.visible
|
||||||
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState
|
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,4 +196,8 @@ class ViewCallScreenMediator(
|
|||||||
override fun showWifiToCellularPopupWindow() {
|
override fun showWifiToCellularPopupWindow() {
|
||||||
wifiToCellularPopupWindow.show()
|
wifiToCellularPopupWindow.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hideMissingPermissionsNotice() {
|
||||||
|
callScreen.findViewById<View>(R.id.missing_permissions_container).visible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
@@ -85,7 +84,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|||||||
import org.thoughtcrime.securesms.util.ThrottledDebouncer
|
import org.thoughtcrime.securesms.util.ThrottledDebouncer
|
||||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||||
import org.thoughtcrime.securesms.util.WindowUtil
|
import org.thoughtcrime.securesms.util.WindowUtil
|
||||||
import org.thoughtcrime.securesms.util.visible
|
|
||||||
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState
|
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.ChosenAudioDeviceIdentifier
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.ChosenAudioDeviceIdentifier
|
||||||
@@ -386,6 +384,7 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
|||||||
|
|
||||||
viewModel.setRecipient(event.recipient)
|
viewModel.setRecipient(event.recipient)
|
||||||
callScreen.setRecipient(event.recipient)
|
callScreen.setRecipient(event.recipient)
|
||||||
|
event.isRemoteVideoOffer
|
||||||
callScreen.setWebRtcCallState(event.state)
|
callScreen.setWebRtcCallState(event.state)
|
||||||
|
|
||||||
when (event.state) {
|
when (event.state) {
|
||||||
@@ -669,7 +668,7 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
|||||||
callPermissionsDialogController.requestCameraAndAudioPermission(
|
callPermissionsDialogController.requestCameraAndAudioPermission(
|
||||||
activity = this,
|
activity = this,
|
||||||
onAllGranted = onGranted,
|
onAllGranted = onGranted,
|
||||||
onCameraGranted = { findViewById<View>(R.id.missing_permissions_container).visible = false },
|
onCameraGranted = { callScreen.hideMissingPermissionsNotice() },
|
||||||
onAudioDenied = this::handleDenyCall
|
onAudioDenied = this::handleDenyCall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -677,7 +676,7 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
|||||||
private fun askCameraPermissions(onGranted: () -> Unit) {
|
private fun askCameraPermissions(onGranted: () -> Unit) {
|
||||||
callPermissionsDialogController.requestCameraPermission(this) {
|
callPermissionsDialogController.requestCameraPermission(this) {
|
||||||
onGranted()
|
onGranted()
|
||||||
findViewById<View>(R.id.missing_permissions_container).visible = false
|
callScreen.hideMissingPermissionsNotice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<color name="story_caption_gradient_start">#CC000000</color>
|
<color name="story_caption_gradient_start">#CC000000</color>
|
||||||
|
|
||||||
<color name="webrtc_hangup_background">#F07168</color>
|
<color name="webrtc_hangup_background">#F07168</color>
|
||||||
|
<color name="webrtc_answer_background">#34C759</color>
|
||||||
|
|
||||||
<color name="transparent_black_05">#0D000000</color>
|
<color name="transparent_black_05">#0D000000</color>
|
||||||
<color name="transparent_black_08">#14000000</color>
|
<color name="transparent_black_08">#14000000</color>
|
||||||
|
|||||||
Reference in New Issue
Block a user