Add visibility rules and timeout for call controls for v2.
This commit is contained in:
parent
3f71f90234
commit
5f66e2eb15
5 changed files with 115 additions and 44 deletions
|
@ -18,8 +18,10 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rxjava3.subscribeAsState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
|
@ -36,11 +38,13 @@ import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState
|
|||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
|
||||
import org.thoughtcrime.securesms.components.webrtc.controls.CallInfoView
|
||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel
|
||||
import org.thoughtcrime.securesms.components.webrtc.controls.RaiseHandSnackbar
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel
|
||||
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage
|
||||
|
@ -77,6 +81,8 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val fullscreenHelper = FullscreenHelper(this)
|
||||
|
||||
lifecycleDisposable.bindTo(this)
|
||||
val compositeDisposable = CompositeDisposable()
|
||||
lifecycleDisposable.add(compositeDisposable)
|
||||
|
@ -120,10 +126,20 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
|||
}
|
||||
}
|
||||
|
||||
var areControlsVisible by remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(areControlsVisible) {
|
||||
if (areControlsVisible) {
|
||||
fullscreenHelper.showSystemUI()
|
||||
} else {
|
||||
fullscreenHelper.hideSystemUI()
|
||||
}
|
||||
}
|
||||
|
||||
SignalTheme {
|
||||
Surface {
|
||||
CallScreen(
|
||||
callRecipient = recipient ?: Recipient.UNKNOWN,
|
||||
callRecipient = recipient,
|
||||
webRtcCallState = callParticipantsState.callState,
|
||||
callScreenState = callScreenState,
|
||||
callControlsState = callControlsState,
|
||||
|
@ -146,8 +162,16 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
|||
.alpha(it)
|
||||
)
|
||||
},
|
||||
raiseHandSnackbar = {
|
||||
RaiseHandSnackbar.View(
|
||||
webRtcCallViewModel = webRtcCallViewModel,
|
||||
showCallInfoListener = { /*TODO*/ },
|
||||
modifier = it
|
||||
)
|
||||
},
|
||||
onNavigationClick = { finish() },
|
||||
onLocalPictureInPictureClicked = webRtcCallViewModel::onLocalPictureInPictureClicked
|
||||
onLocalPictureInPictureClicked = webRtcCallViewModel::onLocalPictureInPictureClicked,
|
||||
onControlsToggled = { areControlsVisible = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,19 @@ import android.content.res.Configuration
|
|||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -41,10 +48,20 @@ fun CallControls(
|
|||
) {
|
||||
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
|
||||
val density = LocalDensity.current
|
||||
val padBottom = with(density) { WindowInsets.navigationBars.getBottom(density).toDp() }
|
||||
var bottom by remember {
|
||||
mutableStateOf(padBottom)
|
||||
}
|
||||
|
||||
if (padBottom != 0.dp) {
|
||||
bottom = padBottom
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = spacedBy(30.dp),
|
||||
modifier = modifier.navigationBarsPadding()
|
||||
modifier = modifier.padding(bottom = bottom)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = spacedBy(20.dp)
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.webrtc.v2
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
@ -19,12 +20,10 @@ import androidx.compose.foundation.layout.BoxScope
|
|||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
|
@ -38,10 +37,12 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -50,11 +51,10 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Previews
|
||||
|
@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel
|
|||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import kotlin.math.max
|
||||
import kotlin.math.round
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
private const val DRAG_HANDLE_HEIGHT = 22
|
||||
private const val SHEET_TOP_PADDING = 9
|
||||
|
@ -86,19 +87,26 @@ fun CallScreen(
|
|||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState,
|
||||
callInfoView: @Composable (Float) -> Unit,
|
||||
raiseHandSnackbar: @Composable (Modifier) -> Unit,
|
||||
onNavigationClick: () -> Unit,
|
||||
onLocalPictureInPictureClicked: () -> Unit
|
||||
onLocalPictureInPictureClicked: () -> Unit,
|
||||
onControlsToggled: (Boolean) -> Unit
|
||||
) {
|
||||
var peekPercentage by remember {
|
||||
mutableFloatStateOf(0f)
|
||||
}
|
||||
|
||||
val skipHiddenState by rememberUpdatedState(newValue = callControlsState.skipHiddenState)
|
||||
val valueChangeOperation: (SheetValue) -> Boolean = remember {
|
||||
{
|
||||
!(it == SheetValue.Hidden && skipHiddenState)
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = rememberStandardBottomSheetState(
|
||||
confirmValueChange = {
|
||||
!(it == SheetValue.Hidden && callControlsState.skipHiddenState)
|
||||
},
|
||||
confirmValueChange = valueChangeOperation,
|
||||
skipHiddenState = false
|
||||
)
|
||||
)
|
||||
|
@ -169,7 +177,8 @@ fun CallScreen(
|
|||
overflowParticipants = overflowParticipants,
|
||||
scaffoldState = scaffoldState,
|
||||
callControlsState = callControlsState,
|
||||
onPipClick = onLocalPictureInPictureClicked
|
||||
onPipClick = onLocalPictureInPictureClicked,
|
||||
onControlsToggled = onControlsToggled
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -187,7 +196,8 @@ fun CallScreen(
|
|||
overflowParticipants = overflowParticipants,
|
||||
scaffoldState = scaffoldState,
|
||||
callControlsState = callControlsState,
|
||||
onPipClick = onLocalPictureInPictureClicked
|
||||
onPipClick = onLocalPictureInPictureClicked,
|
||||
onControlsToggled = onControlsToggled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -203,13 +213,19 @@ fun CallScreen(
|
|||
}
|
||||
|
||||
if (webRtcCallState.isPassedPreJoin) {
|
||||
CallScreenTopBar(
|
||||
callRecipient = callRecipient,
|
||||
callStatus = callScreenState.callStatus,
|
||||
onNavigationClick = onNavigationClick,
|
||||
onCallInfoClick = onCallInfoClick,
|
||||
modifier = Modifier.padding(bottom = padding)
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = scaffoldState.bottomSheetState.targetValue != SheetValue.Hidden,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
CallScreenTopBar(
|
||||
callRecipient = callRecipient,
|
||||
callStatus = callScreenState.callStatus,
|
||||
onNavigationClick = onNavigationClick,
|
||||
onCallInfoClick = onCallInfoClick,
|
||||
modifier = Modifier.padding(bottom = padding)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
CallScreenPreJoinOverlay(
|
||||
callRecipient = callRecipient,
|
||||
|
@ -221,6 +237,8 @@ fun CallScreen(
|
|||
)
|
||||
}
|
||||
|
||||
raiseHandSnackbar(Modifier.fillMaxWidth())
|
||||
|
||||
AnimatedCallStateUpdate(
|
||||
callControlsChange = callScreenState.callControlsChange,
|
||||
modifier = Modifier
|
||||
|
@ -246,7 +264,8 @@ private fun BoxScope.Viewport(
|
|||
overflowParticipants: List<CallParticipant>,
|
||||
scaffoldState: BottomSheetScaffoldState,
|
||||
callControlsState: CallControlsState,
|
||||
onPipClick: () -> Unit
|
||||
onPipClick: () -> Unit,
|
||||
onControlsToggled: (Boolean) -> Unit
|
||||
) {
|
||||
if (webRtcCallState.isPreJoinOrNetworkUnavailable) {
|
||||
LargeLocalVideoRenderer(
|
||||
|
@ -260,6 +279,15 @@ private fun BoxScope.Viewport(
|
|||
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val hideSheet by rememberUpdatedState(newValue = scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded && !callControlsState.skipHiddenState)
|
||||
LaunchedEffect(hideSheet) {
|
||||
if (hideSheet) {
|
||||
delay(5.seconds)
|
||||
scaffoldState.bottomSheetState.hide()
|
||||
onControlsToggled(false)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
|
@ -275,7 +303,9 @@ private fun BoxScope.Viewport(
|
|||
scope.launch {
|
||||
if (scaffoldState.bottomSheetState.isVisible) {
|
||||
scaffoldState.bottomSheetState.hide()
|
||||
onControlsToggled(false)
|
||||
} else {
|
||||
onControlsToggled(true)
|
||||
scaffoldState.bottomSheetState.show()
|
||||
}
|
||||
}
|
||||
|
@ -317,17 +347,9 @@ private fun BoxScope.Viewport(
|
|||
}
|
||||
|
||||
if (webRtcCallState.inOngoingCall && localParticipant.isVideoEnabled && !isLargeGroupCall) {
|
||||
val padBottom: Dp = if (scaffoldState.bottomSheetState.isVisible) {
|
||||
0.dp
|
||||
} else {
|
||||
val density = LocalDensity.current
|
||||
with(density) { WindowInsets.navigationBars.getBottom(density).toDp() }
|
||||
}
|
||||
|
||||
SmallMoveableLocalVideoRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState,
|
||||
extraPadBottom = padBottom,
|
||||
onClick = onPipClick
|
||||
)
|
||||
}
|
||||
|
@ -365,12 +387,14 @@ private fun TinyLocalVideoRenderer(
|
|||
val smallSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(40.dp, 72.dp) else DpSize(72.dp, 40.dp)
|
||||
}
|
||||
val largeSize = remember(isPortrait) {
|
||||
val expandedSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(180.dp, 320.dp) else DpSize(320.dp, 180.dp)
|
||||
}
|
||||
|
||||
val width by animateDpAsState(label = "tiny-width", targetValue = if (localRenderState == WebRtcLocalRenderState.EXPANDED) largeSize.width else smallSize.width)
|
||||
val height by animateDpAsState(label = "tiny-height", targetValue = if (localRenderState == WebRtcLocalRenderState.EXPANDED) largeSize.height else smallSize.height)
|
||||
val size = if (localRenderState == WebRtcLocalRenderState.EXPANDED) expandedSize else smallSize
|
||||
|
||||
val width by animateDpAsState(label = "tiny-width", targetValue = size.width)
|
||||
val height by animateDpAsState(label = "tiny-height", targetValue = size.height)
|
||||
|
||||
LocalParticipantRenderer(
|
||||
localParticipant = localParticipant,
|
||||
|
@ -391,7 +415,6 @@ private fun TinyLocalVideoRenderer(
|
|||
private fun SmallMoveableLocalVideoRenderer(
|
||||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState,
|
||||
extraPadBottom: Dp,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
|
@ -399,15 +422,14 @@ private fun SmallMoveableLocalVideoRenderer(
|
|||
val smallSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(90.dp, 160.dp) else DpSize(160.dp, 90.dp)
|
||||
}
|
||||
val largeSize = remember(isPortrait) {
|
||||
val expandedSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(180.dp, 320.dp) else DpSize(320.dp, 180.dp)
|
||||
}
|
||||
|
||||
val size = if (localRenderState == WebRtcLocalRenderState.SMALL_RECTANGLE) smallSize else largeSize
|
||||
val size = if (localRenderState == WebRtcLocalRenderState.EXPANDED) expandedSize else smallSize
|
||||
|
||||
val targetWidth by animateDpAsState(label = "animate-pip-width", targetValue = size.width, animationSpec = tween())
|
||||
val targetHeight by animateDpAsState(label = "animate-pip-height", targetValue = size.height, animationSpec = tween())
|
||||
val bottomPadding by animateDpAsState(label = "animate-pip-bottom-pad", targetValue = extraPadBottom, animationSpec = tween())
|
||||
|
||||
PictureInPicture(
|
||||
contentSize = DpSize(targetWidth, targetHeight),
|
||||
|
@ -415,7 +437,6 @@ private fun SmallMoveableLocalVideoRenderer(
|
|||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.statusBarsPadding()
|
||||
.padding(bottom = bottomPadding)
|
||||
) {
|
||||
LocalParticipantRenderer(
|
||||
localParticipant = localParticipant,
|
||||
|
@ -482,9 +503,11 @@ private fun CallScreenPreview() {
|
|||
callInfoView = {
|
||||
Text(text = "Call Info View Preview", modifier = Modifier.alpha(it))
|
||||
},
|
||||
raiseHandSnackbar = {},
|
||||
onNavigationClick = {},
|
||||
onLocalPictureInPictureClicked = {},
|
||||
overflowParticipants = (1..5).map { CallParticipant() }
|
||||
overflowParticipants = emptyList(),
|
||||
onControlsToggled = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ 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.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
|
||||
|
@ -47,13 +48,19 @@ fun LocalParticipantRenderer(
|
|||
)
|
||||
}
|
||||
|
||||
val localRecipient = if (LocalInspectionMode.current) {
|
||||
Recipient()
|
||||
} else {
|
||||
localParticipant.recipient
|
||||
}
|
||||
|
||||
val model = remember {
|
||||
ProfileContactPhoto(Recipient.self())
|
||||
ProfileContactPhoto(localRecipient)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val fallback = remember {
|
||||
FallbackAvatarDrawable(context, Recipient.self().getFallbackAvatar())
|
||||
FallbackAvatarDrawable(context, localRecipient.getFallbackAvatar())
|
||||
}
|
||||
|
||||
GlideImage(
|
||||
|
|
|
@ -91,9 +91,9 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
|||
seen.add(Recipient.self());
|
||||
|
||||
for (GroupCall.RemoteDeviceState device : remoteDeviceStates) {
|
||||
Recipient recipient = Recipient.externalPush(ACI.from(device.getUserId()));
|
||||
CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId());
|
||||
CallParticipant callParticipant = participants.get(callParticipantId);
|
||||
Recipient recipient = Recipient.externalPush(ACI.from(device.getUserId()));
|
||||
CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId());
|
||||
CallParticipant callParticipant = participants.get(callParticipantId);
|
||||
|
||||
BroadcastVideoSink videoSink;
|
||||
VideoTrack videoTrack = device.getVideoTrack();
|
||||
|
|
Loading…
Add table
Reference in a new issue