Add confirmation dialog for lowering a raised hand.

This commit is contained in:
Nicholas Tinsley 2023-12-11 17:37:19 -05:00 committed by Cody Henthorne
parent c2f5a6390e
commit 9ed80d46b6
17 changed files with 136 additions and 65 deletions

View file

@ -437,7 +437,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
callStateUpdatePopupWindow = new CallStateUpdatePopupWindow(callScreen);
wifiToCellularPopupWindow = new WifiToCellularPopupWindow(callScreen);
callOverflowPopupWindow = new CallOverflowPopupWindow(this, callScreen);
callOverflowPopupWindow = new CallOverflowPopupWindow(this, callScreen, () -> {
CallParticipantsState state = viewModel.getCallParticipantsStateSnapshot();
if (state == null) {
return false;
}
return state.getLocalParticipant().isHandRaised();
});
}
private void initializeViewModel(boolean isLandscapeEnabled) {

View file

@ -12,22 +12,25 @@ import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.PopupWindowCompat
import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.WebRtcCallActivity
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
/**
* A popup window for calls that holds extra actions, such as reactions, raise hand, and screen sharing.
*
*/
class CallOverflowPopupWindow(private val activity: WebRtcCallActivity, parentViewGroup: ViewGroup) : PopupWindow(
class CallOverflowPopupWindow(private val activity: FragmentActivity, parentViewGroup: ViewGroup, private val raisedHandDelegate: RaisedHandDelegate) : PopupWindow(
LayoutInflater.from(activity).inflate(R.layout.call_overflow_holder, parentViewGroup, false),
activity.resources.getDimension(R.dimen.calling_reaction_popup_menu_width).toInt(),
activity.resources.getDimension(R.dimen.calling_reaction_popup_menu_height).toInt()
) {
private val raiseHandLabel: TextView = (contentView as LinearLayout).findViewById(R.id.raise_hand_label)
init {
val root = (contentView as LinearLayout)
@ -36,8 +39,19 @@ class CallOverflowPopupWindow(private val activity: WebRtcCallActivity, parentVi
dismiss()
}
root.findViewById<ConstraintLayout>(R.id.raise_hand_layout_parent).setOnClickListener {
ApplicationDependencies.getSignalCallManager().raiseHand(true)
dismiss()
if (raisedHandDelegate.isSelfHandRaised()) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.CallOverflowPopupWindow__lower_your_hand)
.setPositiveButton(R.string.CallOverflowPopupWindow__lower_hand) { _, _ ->
ApplicationDependencies.getSignalCallManager().raiseHand(false)
this@CallOverflowPopupWindow.dismiss()
}
.setNegativeButton(R.string.CallOverflowPopupWindow__cancel, null)
.show()
} else {
ApplicationDependencies.getSignalCallManager().raiseHand(true)
dismiss()
}
}
}
@ -58,6 +72,12 @@ class CallOverflowPopupWindow(private val activity: WebRtcCallActivity, parentVi
val xOffset = windowWidth - popupWidth - margin
val yOffset = -popupHeight - margin
raiseHandLabel.setText(if (raisedHandDelegate.isSelfHandRaised()) R.string.CallOverflowPopupWindow__lower_hand else R.string.CallOverflowPopupWindow__raise_hand)
PopupWindowCompat.showAsDropDown(this, anchor, xOffset, yOffset, Gravity.NO_GRAVITY)
}
interface RaisedHandDelegate {
fun isSelfHandRaised(): Boolean
}
}

View file

@ -4,9 +4,11 @@ import android.content.Context
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import com.annimon.stream.OptionalLong
import kotlinx.collections.immutable.toImmutableList
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls.FoldableState
import org.thoughtcrime.securesms.events.CallParticipant
import org.thoughtcrime.securesms.events.CallParticipant.Companion.HAND_LOWERED
import org.thoughtcrime.securesms.events.CallParticipant.Companion.createLocal
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent
import org.thoughtcrime.securesms.events.GroupCallReactionEvent
@ -27,11 +29,10 @@ data class CallParticipantsState(
val callState: WebRtcViewModel.State = WebRtcViewModel.State.CALL_DISCONNECTED,
val groupCallState: WebRtcViewModel.GroupCallState = WebRtcViewModel.GroupCallState.IDLE,
private val remoteParticipants: ParticipantCollection = ParticipantCollection(SMALL_GROUP_MAX),
val localParticipant: CallParticipant = createLocal(CameraState.UNKNOWN, BroadcastVideoSink(), microphoneEnabled = false, isHandRaised = false),
val localParticipant: CallParticipant = createLocal(CameraState.UNKNOWN, BroadcastVideoSink(), microphoneEnabled = false, handRaisedTimestamp = HAND_LOWERED),
val focusedParticipant: CallParticipant = CallParticipant.EMPTY,
val localRenderState: WebRtcLocalRenderState = WebRtcLocalRenderState.GONE,
val reactions: List<GroupCallReactionEvent> = emptyList(),
val raisedHands: List<GroupCallRaiseHandEvent> = emptyList(),
val isInPipMode: Boolean = false,
private val showVideoForOutgoing: Boolean = false,
val isViewingFocusedParticipant: Boolean = false,
@ -50,6 +51,15 @@ data class CallParticipantsState(
val isLargeVideoGroup: Boolean = allRemoteParticipants.size > SMALL_GROUP_MAX
val isIncomingRing: Boolean = callState == WebRtcViewModel.State.CALL_INCOMING
val raisedHands: List<GroupCallRaiseHandEvent>
get() {
val results = allRemoteParticipants.filter { it.isHandRaised }.map { GroupCallRaiseHandEvent(it.recipient, it.handRaisedTimestamp) }.toMutableList()
if (localParticipant.isHandRaised) {
results.add(GroupCallRaiseHandEvent(localParticipant.recipient, localParticipant.handRaisedTimestamp))
}
return results.toImmutableList()
}
val gridParticipants: List<CallParticipant>
get() {
return remoteParticipants.gridParticipants
@ -229,7 +239,6 @@ data class CallParticipantsState(
localRenderState = localRenderState,
showVideoForOutgoing = newShowVideoForOutgoing,
recipient = webRtcViewModel.recipient,
raisedHands = webRtcViewModel.raisedHands,
remoteDevicesCount = webRtcViewModel.remoteDevicesCount,
ringGroup = webRtcViewModel.ringGroup,
isInOutgoingRingingMode = isInOutgoingRingingMode,

View file

@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.components.webrtc.controls
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@ -42,6 +43,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.toLiveData
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Observable
import org.signal.core.ui.Rows
@ -337,9 +339,10 @@ private fun CallParticipantRow(
)
if (showIcons && showHandRaised && canLowerHand) {
val context = LocalContext.current
TextButton(onClick = {
if (recipient.isSelf) {
ApplicationDependencies.getSignalCallManager().raiseHand(false)
showLowerHandDialog(context)
}
}) {
Text(text = stringResource(id = R.string.CallOverflowPopupWindow__lower_hand))
@ -391,6 +394,16 @@ private fun CallParticipantRow(
}
}
private fun showLowerHandDialog(context: Context) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.CallOverflowPopupWindow__lower_your_hand)
.setPositiveButton(
R.string.CallOverflowPopupWindow__lower_hand
) { _, _ -> ApplicationDependencies.getSignalCallManager().raiseHand(false) }
.setNegativeButton(R.string.CallOverflowPopupWindow__cancel, null)
.show()
}
@Composable
private fun GroupMemberRow(
groupMember: GroupMemberEntry.FullMember,

View file

@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.components.webrtc.controls
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
@ -41,6 +42,7 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.core.BackpressureStrategy
import kotlinx.coroutines.delay
import org.signal.core.ui.theme.SignalTheme
@ -50,7 +52,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
/**
* This is a UI element to display the status of one or more people with raised hands in a group call.
@ -149,8 +150,9 @@ private fun RaiseHand(
Spacer(modifier = Modifier.weight(1f))
if (state.raisedHands.first().sender.isSelf) {
val context = LocalContext.current
TextButton(onClick = {
ApplicationDependencies.getSignalCallManager().raiseHand(false)
showLowerHandDialog(context)
}) {
Text(text = stringResource(id = R.string.CallOverflowPopupWindow__lower_hand))
}
@ -167,6 +169,16 @@ private fun RaiseHand(
}
}
private fun showLowerHandDialog(context: Context) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.CallOverflowPopupWindow__lower_your_hand)
.setPositiveButton(
R.string.CallOverflowPopupWindow__lower_hand
) { _, _ -> ApplicationDependencies.getSignalCallManager().raiseHand(false) }
.setNegativeButton(R.string.CallOverflowPopupWindow__cancel, null)
.show()
}
@Composable
private fun getSnackbarText(state: RaiseHandState): String {
if (state.isEmpty()) {

View file

@ -16,7 +16,7 @@ data class CallParticipant constructor(
val isForwardingVideo: Boolean = true,
val isVideoEnabled: Boolean = false,
val isMicrophoneEnabled: Boolean = false,
val isHandRaised: Boolean = false,
val handRaisedTimestamp: Long = HAND_LOWERED,
val lastSpoke: Long = 0,
val audioLevel: AudioLevel? = null,
val isMediaKeysReceived: Boolean = true,
@ -36,6 +36,9 @@ data class CallParticipant constructor(
val isSelf: Boolean
get() = recipient.isSelf
val isHandRaised: Boolean
get() = handRaisedTimestamp > 0
fun getRecipientDisplayName(context: Context): String {
return if (recipient.isSelf && isPrimary) {
context.getString(R.string.CallParticipant__you)
@ -72,6 +75,10 @@ data class CallParticipant constructor(
return copy(isScreenSharing = enable)
}
fun withHandRaisedTimestamp(timestamp: Long): CallParticipant {
return copy(handRaisedTimestamp = timestamp)
}
enum class DeviceOrdinal {
PRIMARY, SECONDARY
}
@ -103,24 +110,28 @@ data class CallParticipant constructor(
}
companion object {
const val HAND_LOWERED = -1L
@JvmField
val EMPTY: CallParticipant = CallParticipant()
@JvmStatic
@JvmOverloads
fun createLocal(
cameraState: CameraState,
renderer: BroadcastVideoSink,
microphoneEnabled: Boolean,
isHandRaised: Boolean
handRaisedTimestamp: Long,
callParticipantId: CallParticipantId = CallParticipantId(Recipient.self())
): CallParticipant {
return CallParticipant(
callParticipantId = CallParticipantId(Recipient.self()),
callParticipantId = callParticipantId,
recipient = Recipient.self(),
videoSink = renderer,
cameraState = cameraState,
isVideoEnabled = cameraState.isEnabled && cameraState.cameraCount > 0,
isMicrophoneEnabled = microphoneEnabled,
isHandRaised = isHandRaised
handRaisedTimestamp = handRaisedTimestamp
)
}
@ -133,7 +144,7 @@ data class CallParticipant constructor(
isForwardingVideo: Boolean,
audioEnabled: Boolean,
videoEnabled: Boolean,
isHandRaised: Boolean,
handRaisedTimestamp: Long,
lastSpoke: Long,
mediaKeysReceived: Boolean,
addedToCallTime: Long,
@ -148,7 +159,7 @@ data class CallParticipant constructor(
isForwardingVideo = isForwardingVideo,
isVideoEnabled = videoEnabled,
isMicrophoneEnabled = audioEnabled,
isHandRaised = isHandRaised,
handRaisedTimestamp = handRaisedTimestamp,
lastSpoke = lastSpoke,
isMediaKeysReceived = mediaKeysReceived,
addedToCallTime = addedToCallTime,

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.events
import com.annimon.stream.OptionalLong
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink
import org.thoughtcrime.securesms.events.CallParticipant.Companion.HAND_LOWERED
import org.thoughtcrime.securesms.events.CallParticipant.Companion.createLocal
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@ -92,7 +93,6 @@ class WebRtcViewModel(state: WebRtcServiceState) {
val isRemoteVideoOffer: Boolean = state.getCallSetupState(state.callInfoState.activePeer?.callId).isRemoteVideoOffer
val callConnectedTime: Long = state.callInfoState.callConnectedTime
val remoteParticipants: List<CallParticipant> = state.callInfoState.remoteCallParticipants
val raisedHands: List<GroupCallRaiseHandEvent> = state.callInfoState.raisedHands
val identityChangedParticipants: Set<RecipientId> = state.callInfoState.identityChangedRecipients
val remoteDevicesCount: OptionalLong = state.callInfoState.remoteDevicesCount
val participantLimit: Long? = state.callInfoState.participantLimit
@ -107,11 +107,11 @@ class WebRtcViewModel(state: WebRtcServiceState) {
val availableDevices: Set<SignalAudioManager.AudioDevice> = state.localDeviceState.availableDevices
val bluetoothPermissionDenied: Boolean = state.localDeviceState.bluetoothPermissionDenied
val localParticipant: CallParticipant = createLocal(
val localParticipant: CallParticipant = state.callInfoState.localParticipant ?: createLocal(
state.localDeviceState.cameraState,
(if (state.videoState.localSink != null) state.videoState.localSink else BroadcastVideoSink())!!,
state.localDeviceState.isMicrophoneEnabled,
state.callInfoState.raisedHands.map { it.sender }.contains(Recipient.self())
HAND_LOWERED
)
val isCellularConnection: Boolean = when (state.localDeviceState.networkConnectionType) {

View file

@ -50,7 +50,7 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
true,
true,
false,
currentState.getCallInfoState().getRaisedHands().contains(remotePeer.getRecipient()),
CallParticipant.HAND_LOWERED,
0,
true,
0,
@ -109,7 +109,7 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
true,
true,
false,
currentState.getCallInfoState().getRaisedHands().contains(remotePeer.getRecipient()),
CallParticipant.HAND_LOWERED,
0,
true,
0,

View file

@ -108,6 +108,8 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
videoSink = new BroadcastVideoSink();
}
long handRaisedTimestamp = callParticipant != null ? callParticipant.getHandRaisedTimestamp() : CallParticipant.HAND_LOWERED;
builder.putParticipant(callParticipantId,
CallParticipant.createRemote(callParticipantId,
recipient,
@ -116,7 +118,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
device.getForwardingVideo() == null || device.getForwardingVideo(),
Boolean.FALSE.equals(device.getAudioMuted()),
Boolean.FALSE.equals(device.getVideoMuted()),
currentState.getCallInfoState().getRaisedHands().contains(recipient),
handRaisedTimestamp,
device.getSpeakerTime(),
device.getMediaKeysReceived(),
device.getAddedTime(),

View file

@ -14,7 +14,6 @@ import org.signal.ringrtc.GroupCall;
import org.signal.ringrtc.PeekInfo;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent;
import org.thoughtcrime.securesms.events.GroupCallReactionEvent;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@ -24,18 +23,13 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.CallInfoState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import ezvcard.util.StringUtils;
/**
* Process actions for when the call has at least once been connected and joined.
@ -266,34 +260,38 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
@Override
protected @NonNull WebRtcServiceState handleGroupCallRaisedHand(@NonNull WebRtcServiceState currentState, List<Long> raisedHands) {
Log.i(tag, "handleGroupCallRaisedHand():");
List<GroupCallRaiseHandEvent> existingHands = currentState.getCallInfoState().getRaisedHands();
Log.i(TAG, "handleGroupCallRaisedHand():");
long now = System.currentTimeMillis();
WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder().changeCallInfoState();
Long localDemuxId = currentState.getCallInfoState().requireGroupCall().getLocalDeviceState().getDemuxId();
List<CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipants();
List<GroupCallRaiseHandEvent> currentRaisedHands = raisedHands
.stream().map(demuxId -> {
if (Objects.equals(demuxId, currentState.getCallInfoState().requireGroupCall().getLocalDeviceState().getDemuxId())) {
return Recipient.self();
}
CallParticipant participant = participants.stream().filter(it -> it.getCallParticipantId().getDemuxId() == demuxId).findFirst().orElse(null);
if (participant == null) {
Log.v(TAG, "Could not find CallParticipantId in list of call participants based on demuxId for raise hand.");
return null;
}
return participant.getRecipient();
})
.filter(Objects::nonNull)
.map(recipient -> {
final Optional<GroupCallRaiseHandEvent> matchingEvent = existingHands.stream().filter(existingEvent -> existingEvent.getSender().equals(recipient)).findFirst();
if (matchingEvent.isPresent()) {
return matchingEvent.get();
} else {
return new GroupCallRaiseHandEvent(recipient, System.currentTimeMillis());
}
})
.collect(Collectors.toList());
for (CallParticipant updatedParticipant : participants) {
boolean isHandCurrentlyRaised = raisedHands.contains(updatedParticipant.getCallParticipantId().getDemuxId());
boolean wasHandAlreadyRaised = updatedParticipant.isHandRaised();
if (isHandCurrentlyRaised && !wasHandAlreadyRaised) {
builder.putParticipant(updatedParticipant.getCallParticipantId(), updatedParticipant.withHandRaisedTimestamp(now));
} else if (!isHandCurrentlyRaised && wasHandAlreadyRaised) {
builder.putParticipant(updatedParticipant.getCallParticipantId(), updatedParticipant.withHandRaisedTimestamp(CallParticipant.HAND_LOWERED));
}
}
return currentState.builder().changeCallInfoState().setRaisedHand(currentRaisedHands).build();
if (localDemuxId != null) {
if (raisedHands.contains(localDemuxId)) {
builder.setLocalParticipant(CallParticipant.createLocal(currentState.getLocalDeviceState().getCameraState(),
currentState.getVideoState().requireLocalSink(),
currentState.getLocalDeviceState().isMicrophoneEnabled(),
now,
new CallParticipantId(localDemuxId, Recipient.self().getId())));
} else {
builder.setLocalParticipant(CallParticipant.createLocal(currentState.getLocalDeviceState().getCameraState(),
currentState.getVideoState().requireLocalSink(),
currentState.getLocalDeviceState().isMicrophoneEnabled(),
CallParticipant.HAND_LOWERED,
new CallParticipantId(localDemuxId, Recipient.self().getId())));
}
}
return builder.build();
}
}

View file

@ -137,7 +137,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
true,
true,
true,
currentState.getCallInfoState().getRaisedHands().contains(recipient),
CallParticipant.HAND_LOWERED,
0,
false,
0,

View file

@ -167,7 +167,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
true,
true,
false,
currentState.getCallInfoState().getRaisedHands().contains(remotePeerGroup.getRecipient()),
CallParticipant.HAND_LOWERED,
0,
true,
0,

View file

@ -5,7 +5,6 @@ import org.signal.ringrtc.CallId
import org.signal.ringrtc.GroupCall
import org.thoughtcrime.securesms.events.CallParticipant
import org.thoughtcrime.securesms.events.CallParticipantId
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent
import org.thoughtcrime.securesms.events.WebRtcViewModel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@ -23,6 +22,7 @@ data class CallInfoState(
var callRecipient: Recipient = Recipient.UNKNOWN,
var callConnectedTime: Long = -1,
@get:JvmName("getRemoteCallParticipantsMap") var remoteParticipants: MutableMap<CallParticipantId, CallParticipant> = mutableMapOf(),
var localParticipant: CallParticipant? = null,
var peerMap: MutableMap<Int, RemotePeer> = mutableMapOf(),
var activePeer: RemotePeer? = null,
var groupCall: GroupCall? = null,
@ -31,8 +31,7 @@ data class CallInfoState(
var remoteDevicesCount: OptionalLong = OptionalLong.empty(),
var participantLimit: Long? = null,
var pendingParticipants: PendingParticipantCollection = PendingParticipantCollection(),
var callLinkDisconnectReason: CallLinkDisconnectReason? = null,
var raisedHands: List<GroupCallRaiseHandEvent> = emptyList()
var callLinkDisconnectReason: CallLinkDisconnectReason? = null
) {
val remoteCallParticipants: List<CallParticipant>

View file

@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.components.webrtc.EglBaseWrapper;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -366,8 +365,8 @@ public class WebRtcServiceStateBuilder {
return this;
}
public @NonNull CallInfoStateBuilder setRaisedHand(@NonNull List<GroupCallRaiseHandEvent> raisedHands) {
toBuild.setRaisedHands(raisedHands);
public @NonNull CallInfoStateBuilder setLocalParticipant(@NonNull CallParticipant callParticipant) {
toBuild.setLocalParticipant(callParticipant);
return this;
}
}

View file

@ -1857,6 +1857,8 @@
<string name="CallOverflowPopupWindow__lower_your_hand">Lower your hand?</string>
<!-- A dialog button to confirm you would like to lower your hand -->
<string name="CallOverflowPopupWindow__lower_hand">Lower hand</string>
<!-- A negative button for a dialog confirming the user wants to lower their hand (withdraw a raised hand) -->
<string name="CallOverflowPopupWindow__cancel">Cancel</string>
<!-- A notification to the user that they successfully raised their hand -->
<string name="CallOverflowPopupWindow__you_raised_your_hand">You raised your hand</string>
<!-- A button to take you to a list of participants with raised hands -->

View file

@ -178,7 +178,7 @@ public class CallParticipantListUpdateTest {
private static CallParticipant createParticipant(long recipientId, long deMuxId, @NonNull CallParticipant.DeviceOrdinal deviceOrdinal) {
Recipient recipient = new Recipient(RecipientId.from(recipientId), mock(RecipientDetails.class), true);
return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(), false, false, false, false, -1, false, 0, false, deviceOrdinal);
return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(), false, false, false, CallParticipant.HAND_LOWERED, -1, false, 0, false, deviceOrdinal);
}
}

View file

@ -243,7 +243,7 @@ public class ParticipantCollectionTest {
false,
false,
false,
false,
CallParticipant.HAND_LOWERED,
lastSpoke,
false,
added,