Fix call link join issue and add denial dialogs into call UI v2.

This commit is contained in:
Alex Hart
2024-09-16 10:24:43 -03:00
committed by Greyson Parrelli
parent 5bd3eda17d
commit cd846f2b6d
5 changed files with 145 additions and 4 deletions

View File

@@ -25,8 +25,10 @@ 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.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -94,6 +96,16 @@ class CallActivity : BaseActivity(), CallControlsCallback {
observeCallEvents()
viewModel.processCallIntent(CallIntent(intent))
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
viewModel.callActions.collect {
when (it) {
CallViewModel.Action.EnableVideo -> onVideoToggleClick(true)
}
}
}
}
setContent {
val lifecycleOwner = LocalLifecycleOwner.current
val callControlsState by webRtcCallViewModel.getCallControlsState(lifecycleOwner).subscribeAsState(initial = CallControlsState())
@@ -139,6 +151,8 @@ class CallActivity : BaseActivity(), CallControlsCallback {
}
}
val callScreenDialogType by viewModel.dialog.collectAsState(CallScreenDialogType.NONE)
SignalTheme {
Surface {
CallScreen(
@@ -156,6 +170,7 @@ class CallActivity : BaseActivity(), CallControlsCallback {
overflowParticipants = callParticipantsState.listParticipants,
localParticipant = callParticipantsState.localParticipant,
localRenderState = callParticipantsState.localRenderState,
callScreenDialogType = callScreenDialogType,
callInfoView = {
CallInfoView.View(
webRtcCallViewModel = webRtcCallViewModel,
@@ -174,7 +189,8 @@ class CallActivity : BaseActivity(), CallControlsCallback {
},
onNavigationClick = { finish() },
onLocalPictureInPictureClicked = webRtcCallViewModel::onLocalPictureInPictureClicked,
onControlsToggled = { areControlsVisible = it }
onControlsToggled = { areControlsVisible = it },
onCallScreenDialogDismissed = viewModel::onCallScreenDialogDismissed
)
}
}

View File

@@ -86,11 +86,13 @@ fun CallScreen(
overflowParticipants: List<CallParticipant>,
localParticipant: CallParticipant,
localRenderState: WebRtcLocalRenderState,
callScreenDialogType: CallScreenDialogType,
callInfoView: @Composable (Float) -> Unit,
raiseHandSnackbar: @Composable (Modifier) -> Unit,
onNavigationClick: () -> Unit,
onLocalPictureInPictureClicked: () -> Unit,
onControlsToggled: (Boolean) -> Unit
onControlsToggled: (Boolean) -> Unit,
onCallScreenDialogDismissed: () -> Unit = {}
) {
var peekPercentage by remember {
mutableFloatStateOf(0f)
@@ -250,6 +252,8 @@ fun CallScreen(
)
}
}
CallScreenDialog(callScreenDialogType, onCallScreenDialogDismissed)
}
/**
@@ -503,6 +507,7 @@ private fun CallScreenPreview() {
callParticipantsPagerState = CallParticipantsPagerState(),
localParticipant = CallParticipant(),
localRenderState = WebRtcLocalRenderState.LARGE,
callScreenDialogType = CallScreenDialogType.NONE,
callInfoView = {
Text(text = "Call Info View Preview", modifier = Modifier.alpha(it))
},

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.webrtc.v2
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import org.signal.core.ui.DarkPreview
import org.signal.core.ui.Dialogs
import org.signal.core.ui.Previews
import org.thoughtcrime.securesms.R
/**
* Displays the current dialog to the user, or nothing.
*/
@Composable
fun CallScreenDialog(
callScreenDialogType: CallScreenDialogType,
onDialogDismissed: () -> Unit
) {
when (callScreenDialogType) {
CallScreenDialogType.NONE -> return
CallScreenDialogType.REMOVED_FROM_CALL_LINK -> RemovedFromCallLinkDialog(onDialogDismissed)
CallScreenDialogType.DENIED_REQUEST_TO_JOIN_CALL_LINK -> DeniedRequestToJoinCallDialog(onDialogDismissed)
}
}
@Composable
private fun RemovedFromCallLinkDialog(onDialogDismissed: () -> Unit = {}) {
Dialogs.SimpleAlertDialog(
title = stringResource(R.string.WebRtcCallActivity__removed_from_call),
body = stringResource(R.string.WebRtcCallActivity__someone_has_removed_you_from_the_call),
confirm = stringResource(android.R.string.ok),
onConfirm = {},
onDismiss = onDialogDismissed
)
}
@Composable
private fun DeniedRequestToJoinCallDialog(onDialogDismissed: () -> Unit = {}) {
Dialogs.SimpleAlertDialog(
title = stringResource(R.string.WebRtcCallActivity__join_request_denied),
body = stringResource(R.string.WebRtcCallActivity__your_request_to_join_this_call_has_been_denied),
confirm = stringResource(android.R.string.ok),
onConfirm = {},
onDismiss = onDialogDismissed
)
}
@DarkPreview
@Composable
private fun RemovedFromCallLinkDialogPreview() {
Previews.Preview {
RemovedFromCallLinkDialog()
}
}
@DarkPreview
@Composable
private fun DeniedRequestToJoinCallDialogPreview() {
Previews.Preview {
DeniedRequestToJoinCallDialog()
}
}
/**
* Enumeration of available call screen dialog types.
*/
enum class CallScreenDialogType {
NONE,
REMOVED_FROM_CALL_LINK,
DENIED_REQUEST_TO_JOIN_CALL_LINK
}

View File

@@ -10,6 +10,8 @@ import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@@ -27,6 +29,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.events.WebRtcViewModel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.CallLinkDisconnectReason
import org.thoughtcrime.securesms.service.webrtc.SignalCallManager
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
@@ -48,13 +51,23 @@ class CallViewModel(
}
private var previousEvent: WebRtcViewModel? = null
private var enableVideoIfAvailable = false
private var lastProcessedIntentTimestamp = 0L
private var lastCallLinkDisconnectDialogShowTime = 0L
private var enterPipOnResume = false
private val internalCallScreenState = MutableStateFlow(CallScreenState())
val callScreenState: StateFlow<CallScreenState> = internalCallScreenState
private val internalDialog = MutableStateFlow(CallScreenDialogType.NONE)
val dialog: StateFlow<CallScreenDialogType> = internalDialog
private val internalCallActions = MutableSharedFlow<Action>()
val callActions: Flow<Action> = internalCallActions
fun consumeEnterPipOnResume(): Boolean {
val enter = enterPipOnResume
enterPipOnResume = false
@@ -127,6 +140,10 @@ class CallViewModel(
}
}
fun onCallScreenDialogDismissed() {
internalDialog.update { CallScreenDialogType.NONE }
}
fun onCallEvent(event: CallEvent) {
when (event) {
CallEvent.DismissSwitchCameraTooltip -> Unit // TODO
@@ -175,12 +192,24 @@ class CallViewModel(
WebRtcViewModel.State.CALL_ONGOING_ELSEWHERE -> handleCallTerminated(HangupMessage.Type.BUSY)
}
// TODO [alex] -- Call link handling block
if (event.callLinkDisconnectReason != null && event.callLinkDisconnectReason.postedAt > lastCallLinkDisconnectDialogShowTime) {
lastCallLinkDisconnectDialogShowTime = System.currentTimeMillis()
when (event.callLinkDisconnectReason) {
is CallLinkDisconnectReason.RemovedFromCall -> internalDialog.update { CallScreenDialogType.REMOVED_FROM_CALL_LINK }
is CallLinkDisconnectReason.DeniedRequestToJoinCall -> internalDialog.update { CallScreenDialogType.DENIED_REQUEST_TO_JOIN_CALL_LINK }
}
}
val enableVideo = event.localParticipant.cameraState.cameraCount > 0 && enableVideoIfAvailable
webRtcCallViewModel.updateFromWebRtcViewModel(event, enableVideo)
// TODO [alex] -- handle enable video
if (enableVideo) {
enableVideoIfAvailable = false
viewModelScope.launch {
internalCallActions.emit(Action.EnableVideo)
}
}
// TODO [alex] -- handle denied bluetooth permission
}
@@ -373,4 +402,15 @@ class CallViewModel(
lastProcessedIntentTimestamp = now
}
/**
* Actions that require activity-level context (for example, to request permissions.)
*/
enum class Action {
/**
* Tries to enable local video via the normal toggle callback. Should display permissions
* dialogs as necessary.
*/
EnableVideo
}
}

View File

@@ -33,6 +33,11 @@ class CallLinkPreJoinActionProcessor(
private val TAG = Log.tag(CallLinkPreJoinActionProcessor::class.java)
}
override fun handleSetRingGroup(currentState: WebRtcServiceState, ringGroup: Boolean): WebRtcServiceState {
Log.i(TAG, "handleSetRingGroup(): Ignoring.")
return currentState
}
override fun handlePreJoinCall(currentState: WebRtcServiceState, remotePeer: RemotePeer): WebRtcServiceState {
Log.i(TAG, "handlePreJoinCall():")