Send reactions.

This commit is contained in:
Nicholas Tinsley
2023-12-07 15:18:05 -05:00
committed by GitHub
parent a749b97707
commit 6aac250990
22 changed files with 414 additions and 34 deletions

View File

@@ -29,7 +29,6 @@ import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Rational;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
@@ -60,6 +59,7 @@ import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
import org.thoughtcrime.securesms.components.webrtc.CallLinkInfoSheet;
import org.thoughtcrime.securesms.components.webrtc.CallLinkProfileKeySender;
import org.thoughtcrime.securesms.components.webrtc.CallOverflowPopupWindow;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow;
@@ -82,6 +82,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -115,7 +116,7 @@ import io.reactivex.rxjava3.disposables.Disposable;
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback, ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
private static final String TAG = Log.tag(WebRtcCallActivity.class);
@@ -140,6 +141,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
private CallStateUpdatePopupWindow callStateUpdatePopupWindow;
private CallOverflowPopupWindow callOverflowPopupWindow;
private WifiToCellularPopupWindow wifiToCellularPopupWindow;
private DeviceOrientationMonitor deviceOrientationMonitor;
@@ -193,7 +195,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
initializeViewModel(isLandscapeEnabled);
initializePictureInPictureParams();
controlsAndInfo = new ControlsAndInfoController(callScreen, viewModel);
controlsAndInfo = new ControlsAndInfoController(callScreen, callOverflowPopupWindow, viewModel);
controlsAndInfo.addVisibilityListener(new FadeCallback());
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.webrtc_call_view_toolbar_text), findViewById(R.id.webrtc_call_view_toolbar_no_text));
@@ -432,6 +434,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
callStateUpdatePopupWindow = new CallStateUpdatePopupWindow(callScreen);
wifiToCellularPopupWindow = new WifiToCellularPopupWindow(callScreen);
callOverflowPopupWindow = new CallOverflowPopupWindow(this, callScreen);
}
private void initializeViewModel(boolean isLandscapeEnabled) {
@@ -947,6 +950,15 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
MessageSender.onMessageSent();
}
@Override
public void onReactWithAnyEmojiDialogDismissed() { /* no-op */ }
@Override
public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
ApplicationDependencies.getSignalCallManager().react(emoji);
callOverflowPopupWindow.dismiss();
}
private final class ControlsListener implements WebRtcCallView.ControlsListener {
@Override
@@ -1034,6 +1046,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
handleAnswerWithAudio();
}
@Override
public void onOverflowClicked() {
controlsAndInfo.toggleOverflowPopup();
}
@Override
public void onAcceptCallPressed() {
if (viewModel.isAnswerWithVideoAvailable()) {

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.webrtc
import android.graphics.Rect
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow
import androidx.core.widget.PopupWindowCompat
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(
LayoutInflater.from(activity).inflate(R.layout.call_overflow_holder, parentViewGroup, false),
activity.resources.getDimension(R.dimen.reaction_scrubber_width).toInt(),
ViewGroup.LayoutParams.WRAP_CONTENT
) {
init {
(contentView as CallReactionScrubber).initialize(activity.supportFragmentManager, activity) {
ApplicationDependencies.getSignalCallManager().react(it)
dismiss()
}
}
fun show(anchor: View) {
isFocusable = true
val resources = activity.resources
val margin = resources.getDimension(R.dimen.calling_reaction_scrubber_margin).toInt()
val windowRect = Rect()
contentView.getWindowVisibleDisplayFrame(windowRect)
val windowWidth = windowRect.width()
val popupWidth = resources.getDimension(R.dimen.reaction_scrubber_width).toInt()
val popupHeight = resources.getDimension(R.dimen.calling_reaction_emoji_height).toInt()
val xOffset = windowWidth - popupWidth - margin
val yOffset = -popupHeight - margin
PopupWindowCompat.showAsDropDown(this, anchor, xOffset, yOffset, Gravity.NO_GRAVITY)
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.webrtc
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentManager
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.emoji.EmojiImageView
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
class CallReactionScrubber @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val emojiViews: Array<EmojiImageView>
private var customEmojiIndex = 0
init {
inflate(context, R.layout.call_overflow_popup, this)
emojiViews = arrayOf(
findViewById(R.id.reaction_1),
findViewById(R.id.reaction_2),
findViewById(R.id.reaction_3),
findViewById(R.id.reaction_4),
findViewById(R.id.reaction_5),
findViewById(R.id.reaction_6),
findViewById(R.id.reaction_7)
)
customEmojiIndex = emojiViews.size - 1
}
fun initialize(fragmentManager: FragmentManager, callback: ReactWithAnyEmojiBottomSheetDialogFragment.Callback, listener: (String) -> Unit) {
val emojis = SignalStore.emojiValues().reactions
for (i in emojiViews.indices) {
val view = emojiViews[i]
val isAtCustomIndex = i == customEmojiIndex
if (isAtCustomIndex) {
view.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_any_emoji_32))
view.setOnClickListener {
val bottomSheet = ReactWithAnyEmojiBottomSheetDialogFragment.createForCallingReactions()
bottomSheet.show(fragmentManager, "CallReaction")
}
} else {
val preferredVariation = SignalStore.emojiValues().getPreferredVariation(emojis[i])
view.setImageEmoji(preferredVariation)
view.setOnClickListener { listener(preferredVariation) }
}
}
}
}

View File

@@ -37,7 +37,6 @@ import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.google.android.material.button.MaterialButton;
import com.google.common.collect.Sets;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.SetUtil;
@@ -98,6 +97,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
private ImageView cameraDirectionToggle;
private AccessibleToggleButton ringToggle;
private PictureInPictureGestureHelper pictureInPictureGestureHelper;
private ImageView overflow;
private ImageView hangup;
private View answerWithoutVideo;
private View topGradient;
@@ -179,6 +179,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
answerWithoutVideoLabel = findViewById(R.id.call_screen_answer_without_video_label);
cameraDirectionToggle = findViewById(R.id.call_screen_camera_direction_toggle);
ringToggle = findViewById(R.id.call_screen_audio_ring_toggle);
overflow = findViewById(R.id.call_screen_overflow_button);
hangup = findViewById(R.id.call_screen_end_call);
answerWithoutVideo = findViewById(R.id.call_screen_answer_without_video);
topGradient = findViewById(R.id.call_screen_header_gradient);
@@ -278,6 +279,10 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
cameraDirectionToggle.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onCameraDirectionChanged));
overflow.setOnClickListener(v -> {
runIfNonNull(controlsListener, ControlsListener::onOverflowClicked);
});
hangup.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onEndCallPressed));
decline.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onDenyCallPressed));
@@ -346,6 +351,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
return false;
});
rotatableControls.add(overflow);
rotatableControls.add(hangup);
rotatableControls.add(answer);
rotatableControls.add(answerWithoutVideo);
@@ -596,6 +602,10 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
}
}
public @NonNull View getPopupAnchor() {
return aboveControlsGuideline;
}
public void setStatusFromHangupType(@NonNull HangupMessage.Type hangupType) {
switch (hangupType) {
case NORMAL:
@@ -717,6 +727,10 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
visibleViewSet.add(footerGradient);
}
if (webRtcControls.displayOverflow()) {
visibleViewSet.add(overflow);
}
if (webRtcControls.displayMuteAudio()) {
visibleViewSet.add(micToggle);
}
@@ -893,6 +907,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
private void updateButtonStateForLargeButtons() {
cameraDirectionToggle.setImageResource(R.drawable.webrtc_call_screen_camera_toggle);
hangup.setImageResource(R.drawable.webrtc_call_screen_hangup);
overflow.setImageResource(R.drawable.webrtc_call_screen_overflow_menu);
micToggle.setBackgroundResource(R.drawable.webrtc_call_screen_mic_toggle);
videoToggle.setBackgroundResource(R.drawable.webrtc_call_screen_video_toggle);
audioToggle.setImageResource(R.drawable.webrtc_call_screen_speaker_toggle);
@@ -902,6 +917,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
private void updateButtonStateForSmallButtons() {
cameraDirectionToggle.setImageResource(R.drawable.webrtc_call_screen_camera_toggle_small);
hangup.setImageResource(R.drawable.webrtc_call_screen_hangup_small);
overflow.setImageResource(R.drawable.webrtc_call_screen_overflow_menu_small);
micToggle.setBackgroundResource(R.drawable.webrtc_call_screen_mic_toggle_small);
videoToggle.setBackgroundResource(R.drawable.webrtc_call_screen_video_toggle_small);
audioToggle.setImageResource(R.drawable.webrtc_call_screen_speaker_toggle_small);
@@ -938,6 +954,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
void onAudioOutputChanged31(@NonNull WebRtcAudioDevice audioOutput);
void onVideoChanged(boolean isVideoEnabled);
void onMicChanged(boolean isMicEnabled);
void onOverflowClicked();
void onCameraDirectionChanged();
void onEndCallPressed();
void onDenyCallPressed();

View File

@@ -8,6 +8,7 @@ import androidx.annotation.Px;
import androidx.annotation.StringRes;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
import java.util.Set;
@@ -155,6 +156,10 @@ public final class WebRtcControls {
return isAtLeastOutgoing() || callState == CallState.RECONNECTING;
}
public boolean displayOverflow() {
return FeatureFlags.groupCallReactions() && isAtLeastOutgoing();
}
public boolean displayMuteAudio() {
return isPreJoin() || isAtLeastOutgoing();
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.emoji.EmojiImageView
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.events.GroupCallReactionEvent
import org.thoughtcrime.securesms.recipients.Recipient
/**
* RecyclerView adapter for the reactions feed. This takes in a list of [GroupCallReactionEvent] and renders them onto the screen.
@@ -48,11 +49,19 @@ class WebRtcReactionsRecyclerAdapter : ListAdapter<GroupCallReactionEvent, WebRt
private val textView: EmojiTextView = itemView.findViewById(R.id.webrtc_call_reaction_name_textview)
fun bind(item: GroupCallReactionEvent) {
emojiView.setImageEmoji(item.reaction)
textView.text = item.sender.getRecipientDisplayNameDeviceAgnostic(itemView.context)
textView.text = getName(item.sender)
itemView.isClickable = false
textView.isClickable = false
emojiView.isClickable = false
}
private fun getName(recipient: Recipient): String {
return if (recipient.isSelf) {
itemView.context.getString(R.string.CallParticipant__you)
} else {
recipient.getDisplayName(itemView.context)
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<GroupCallReactionEvent>() {

View File

@@ -28,6 +28,7 @@ import io.reactivex.rxjava3.disposables.Disposable
import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.InsetAwareConstraintLayout
import org.thoughtcrime.securesms.components.webrtc.CallOverflowPopupWindow
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls
@@ -41,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds
*/
class ControlsAndInfoController(
private val webRtcCallView: WebRtcCallView,
private val overflowPopupWindow: CallOverflowPopupWindow,
private val viewModel: WebRtcCallViewModel
) : Disposable {
@@ -122,6 +124,7 @@ class ControlsAndInfoController(
behavior.addBottomSheetCallback(object : BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
overflowPopupWindow.dismiss()
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
if (controlState.isFadeOutEnabled) {
hide(delay = HIDE_CONTROL_DELAY)
@@ -149,6 +152,10 @@ class ControlsAndInfoController(
}
}
})
overflowPopupWindow.setOnDismissListener {
hide(delay = HIDE_CONTROL_DELAY)
}
}
fun addVisibilityListener(listener: BottomSheetVisibilityListener): Boolean {
@@ -191,6 +198,15 @@ class ControlsAndInfoController(
}
}
fun toggleOverflowPopup() {
if (overflowPopupWindow.isShowing) {
overflowPopupWindow.dismiss()
} else {
cancelScheduledHide()
overflowPopupWindow.show(webRtcCallView.popupAnchor)
}
}
fun updateControls(newControlState: WebRtcControls) {
val previousState = controlState
controlState = newControlState

View File

@@ -47,14 +47,6 @@ data class CallParticipant constructor(
}
}
fun getRecipientDisplayNameDeviceAgnostic(context: Context): String {
return if (recipient.isSelf) {
context.getString(R.string.CallParticipant__you)
} else {
recipient.getDisplayName(context)
}
}
fun getShortRecipientDisplayName(context: Context): String {
return if (recipient.isSelf && isPrimary) {
context.getString(R.string.CallParticipant__you)

View File

@@ -5,13 +5,14 @@
package org.thoughtcrime.securesms.events
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.concurrent.TimeUnit
/**
* This is a data class to represent a reaction coming in over the wire in the format we need (mapped to a [CallParticipant]) in a way that can be easily
* This is a data class to represent a reaction coming in over the wire in the format we need (mapped to a [Recipient]) in a way that can be easily
* compared across Rx streams.
*/
data class GroupCallReactionEvent(val sender: CallParticipant, val reaction: String, val timestamp: Long) {
data class GroupCallReactionEvent(val sender: Recipient, val reaction: String, val timestamp: Long) {
fun getExpirationTimestamp(): Long {
return timestamp + TimeUnit.SECONDS.toMillis(LIFESPAN_SECONDS)
}

View File

@@ -59,7 +59,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
private static final String ARG_EDIT = "arg_edit";
private ReactWithAnyEmojiViewModel viewModel;
private Callback callback;
private Callback callback = null;
private EmojiPageView emojiPageView;
private KeyboardPageSearchView search;
private View tabBar;
@@ -123,6 +123,20 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
return fragment;
}
public static ReactWithAnyEmojiBottomSheetDialogFragment createForCallingReactions() {
ReactWithAnyEmojiBottomSheetDialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
Bundle args = new Bundle();
args.putLong(ARG_MESSAGE_ID, -1);
args.putBoolean(ARG_IS_MMS, false);
args.putInt(ARG_START_PAGE, -1);
args.putBoolean(ARG_SHADOWS, false);
args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
@@ -229,8 +243,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
callback.onReactWithAnyEmojiDialogDismissed();
if (callback != null) callback.onReactWithAnyEmojiDialogDismissed();
}
private void initializeViewModel() {
@@ -244,7 +257,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
@Override
public void onEmojiSelected(String emoji) {
viewModel.onEmojiSelected(emoji);
callback.onReactWithAnyEmojiSelected(emoji);
if (callback != null) callback.onReactWithAnyEmojiSelected(emoji);
dismiss();
}

View File

@@ -20,16 +20,16 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.ringrtc.Camera;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Process actions for when the call has at least once been connected and joined.
@@ -202,10 +202,25 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
return terminateGroupCall(currentState);
}
@Override
protected @NonNull WebRtcEphemeralState handleSendGroupReact(@NonNull WebRtcServiceState currentState, @NonNull WebRtcEphemeralState ephemeralState, @NonNull String reaction) {
try {
currentState.getCallInfoState().requireGroupCall().react(reaction);
List<GroupCallReactionEvent> reactionList = ephemeralState.getUnexpiredReactions();
reactionList.add(new GroupCallReactionEvent(Recipient.self(), reaction, System.currentTimeMillis()));
return ephemeralState.copy(ephemeralState.getLocalAudioLevel(), ephemeralState.getRemoteAudioLevels(), reactionList);
} catch (CallException e) {
Log.w(TAG,"Unable to send reaction in group call", e);
}
return ephemeralState;
}
@Override
protected @NonNull WebRtcEphemeralState handleGroupCallReaction(@NonNull WebRtcServiceState currentState, @NonNull WebRtcEphemeralState ephemeralState, List<GroupCall.Reaction> reactions) {
List<GroupCallReactionEvent> reactionList = ephemeralState.getUnexpiredReactions();
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
List<GroupCallReactionEvent> reactionList = ephemeralState.getUnexpiredReactions();
List<CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipants();
for (GroupCall.Reaction reaction : reactions) {
final GroupCallReactionEvent event = createGroupCallReaction(participants, reaction);
@@ -218,13 +233,13 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
}
@Nullable
private GroupCallReactionEvent createGroupCallReaction(Map<CallParticipantId, CallParticipant> participants, final GroupCall.Reaction reaction) {
CallParticipantId participantId = participants.keySet().stream().filter(participant -> participant.getDemuxId() == reaction.demuxId).findFirst().orElse(null);
if (participantId == null) {
private GroupCallReactionEvent createGroupCallReaction(Collection<CallParticipant> participants, final GroupCall.Reaction reaction) {
CallParticipant participant = participants.stream().filter(it -> it.getCallParticipantId().getDemuxId() == reaction.demuxId).findFirst().orElse(null);
if (participant == null) {
Log.v(TAG, "Could not find CallParticipantId in list of call participants based on demuxId for reaction.");
return null;
}
return new GroupCallReactionEvent(participants.get(participantId), reaction.value, System.currentTimeMillis());
return new GroupCallReactionEvent(participant.getRecipient(), reaction.value, System.currentTimeMillis());
}
}

View File

@@ -295,8 +295,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
process((s, p) -> p.handleScreenOffChange(s));
}
public void react() {
process((s, p) -> p.handleSendGroupReact(s));
public void react(@NonNull String reaction) {
processStateless(s -> serviceState.getActionProcessor().handleSendGroupReact(serviceState, s, reaction));
}
public void postStateUpdate(@NonNull WebRtcServiceState state) {

View File

@@ -546,9 +546,9 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleSendGroupReact(@NonNull WebRtcServiceState currentState) {
protected @NonNull WebRtcEphemeralState handleSendGroupReact(@NonNull WebRtcServiceState currentState, @NonNull WebRtcEphemeralState ephemeralState, @NonNull String reaction) {
Log.i(tag, "react not processed");
return currentState;
return ephemeralState;
}
public @NonNull WebRtcServiceState handleCameraSwitchCompleted(@NonNull WebRtcServiceState currentState, @NonNull CameraState newCameraState) {

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M5.13 13.63c0.9 0 1.62-0.73 1.62-1.63 0-0.9-0.73-1.63-1.63-1.63-0.9 0-1.62 0.73-1.62 1.63 0 0.9 0.73 1.63 1.63 1.63Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M13.63 12c0 0.9-0.73 1.63-1.63 1.63-0.9 0-1.63-0.73-1.63-1.63 0-0.9 0.73-1.63 1.63-1.63 0.9 0 1.63 0.73 1.63 1.63Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M20.5 12c0 0.9-0.73 1.63-1.63 1.63-0.9 0-1.62-0.73-1.62-1.63 0-0.9 0.73-1.63 1.63-1.63 0.9 0 1.62 0.73 1.62 1.63Z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/signal_light_colorOnPrimary"
android:pathData="M5.13 13.63c0.9 0 1.62-0.73 1.62-1.63 0-0.9-0.73-1.63-1.63-1.63-0.9 0-1.62 0.73-1.62 1.63 0 0.9 0.73 1.63 1.63 1.63Z" />
<path
android:fillColor="@color/signal_light_colorOnPrimary"
android:pathData="M13.63 12c0 0.9-0.73 1.63-1.63 1.63-0.9 0-1.63-0.73-1.63-1.63 0-0.9 0.73-1.63 1.63-1.63 0.9 0 1.63 0.73 1.63 1.63Z" />
<path
android:fillColor="@color/signal_light_colorOnPrimary"
android:pathData="M20.5 12c0 0.9-0.73 1.63-1.63 1.63-0.9 0-1.62-0.73-1.62-1.63 0-0.9 0.73-1.63 1.63-1.63 0.9 0 1.62 0.73 1.62 1.63Z" />
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/webrtc_call_screen_circle_checked" />
<item
android:bottom="14dp"
android:drawable="@drawable/symbol_more_white_24"
android:left="14dp"
android:right="14dp"
android:top="14dp" />
</layer-list>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_checked" />
<item
android:bottom="14dp"
android:drawable="@drawable/symbol_more_white_24"
android:left="14dp"
android:right="14dp"
android:top="14dp" />
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<org.thoughtcrime.securesms.components.webrtc.CallReactionScrubber
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/conversation_reaction_overlay_background"
android:elevation="4dp"
/>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_1"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reaction_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1"
tools:translationY="0dp" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_2"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reaction_3"
app:layout_constraintStart_toEndOf="@id/reaction_1"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_3"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reaction_4"
app:layout_constraintStart_toEndOf="@id/reaction_2"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_4"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reaction_5"
app:layout_constraintStart_toEndOf="@id/reaction_3"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_5"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reaction_6"
app:layout_constraintStart_toEndOf="@id/reaction_4"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_6"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reaction_7"
app:layout_constraintStart_toEndOf="@id/reaction_5"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/reaction_7"
android:layout_width="32dp"
android:layout_height="@dimen/calling_reaction_emoji_height"
app:forceJumbo="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/reaction_6"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1" />
</merge>

View File

@@ -124,10 +124,30 @@
android:stateListAnimator="@null"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/call_screen_start_call_controls"
app:layout_constraintEnd_toStartOf="@id/call_screen_end_call"
app:layout_constraintEnd_toStartOf="@id/call_screen_overflow_button"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/call_screen_audio_mic_toggle" />
<ImageView
android:id="@+id/call_screen_overflow_button"
android:layout_width="@dimen/webrtc_button_size"
android:layout_height="@dimen/webrtc_button_size"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="30dp"
android:clickable="false"
android:contentDescription="@string/WebRtcCallView__additional_actions"
android:scaleType="fitXY"
android:visibility="gone"
android:background="@drawable/webrtc_call_screen_circle_checked"
app:layout_constraintBottom_toTopOf="@id/call_screen_start_call_controls"
app:layout_constraintEnd_toStartOf="@id/call_screen_end_call"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/call_screen_audio_ring_toggle"
app:srcCompat="@drawable/symbol_more_24"
tools:visibility="visible" />
<ImageView
android:id="@+id/call_screen_end_call"
android:layout_width="@dimen/webrtc_button_size"
@@ -142,7 +162,7 @@
app:layout_constraintBottom_toTopOf="@id/call_screen_start_call_controls"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/call_screen_audio_ring_toggle"
app:layout_constraintStart_toEndOf="@id/call_screen_overflow_button"
app:srcCompat="@drawable/webrtc_call_screen_hangup"
tools:visibility="visible" />

View File

@@ -79,6 +79,8 @@
<dimen name="reaction_scrubber_anim_start_translation_y">25dp</dimen>
<dimen name="reaction_scrubber_anim_end_translation_y">0dp</dimen>
<dimen name="reaction_scrubber_width">320dp</dimen>
<dimen name="calling_reaction_scrubber_margin">4dp</dimen>
<dimen name="calling_reaction_emoji_height">48dp</dimen>
<dimen name="conversation_item_reply_size">38dp</dimen>
<dimen name="conversation_item_avatar_size">28dp</dimen>

View File

@@ -1831,6 +1831,8 @@
<string name="WebRtcCallView__toggle_camera">Toggle camera</string>
<!-- Toggle content description for toggling mute state -->
<string name="WebRtcCallView__toggle_mute">Toggle mute</string>
<!-- Content description for additional actions menu button -->
<string name="WebRtcCallView__additional_actions">Additional actions</string>
<!-- Content description for end-call button -->
<string name="WebRtcCallView__end_call">End call</string>