Move switch camera button to self pip.

This commit is contained in:
Cody Henthorne
2023-12-08 10:37:05 -05:00
parent e22ff1bbfe
commit 7bba4ed820
9 changed files with 166 additions and 42 deletions

View File

@@ -148,6 +148,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private FullscreenHelper fullscreenHelper;
private WebRtcCallView callScreen;
private TooltipPopup videoTooltip;
private TooltipPopup switchCameraTooltip;
private WebRtcCallViewModel viewModel;
private boolean enableVideoIfAvailable;
private boolean hasWarnedAboutBluetooth;
@@ -549,6 +550,20 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
} else if (event instanceof WebRtcCallViewModel.Event.ShowWifiToCellularPopup) {
wifiToCellularPopupWindow.show();
} else if (event instanceof WebRtcCallViewModel.Event.ShowSwitchCameraTooltip) {
if (switchCameraTooltip == null) {
switchCameraTooltip = TooltipPopup.forTarget(callScreen.getSwitchCameraTooltipTarget())
.setBackgroundTint(ContextCompat.getColor(this, R.color.core_ultramarine))
.setTextColor(ContextCompat.getColor(this, R.color.core_white))
.setText(R.string.WebRtcCallActivity__flip_camera_tooltip)
.setOnDismissListener(() -> viewModel.onDismissedSwitchCameraTooltip())
.show(TooltipPopup.POSITION_ABOVE);
}
} else if (event instanceof WebRtcCallViewModel.Event.DismissSwitchCameraTooltip) {
if (switchCameraTooltip != null) {
switchCameraTooltip.dismiss();
switchCameraTooltip = null;
}
} else {
throw new IllegalArgumentException("Unknown event: " + event);
}

View File

@@ -171,21 +171,23 @@ public class TooltipPopup extends PopupWindow {
ShapeAppearanceModel.Builder shapeAppearanceModel = ShapeAppearanceModel.builder()
.setAllCornerSizes(DimensionUnit.DP.toPixels(18));
// If the arrow is within the last 20dp of the right hand side, use RIGHT and set corner to 9dp
onLayout(() -> {
if (arrow.getX() > getContentView().getWidth() / 2f) {
arrow.setImageResource(R.drawable.ic_tooltip_arrow_up_right);
}
if (position == POSITION_BELOW) {
// If the arrow is within the last 20dp of the right hand side, use RIGHT and set corner to 9dp
onLayout(() -> {
if (arrow.getX() > getContentView().getWidth() / 2f) {
arrow.setImageResource(R.drawable.ic_tooltip_arrow_up_right);
}
float arrowEnd = arrow.getX() + arrow.getRight();
if (arrowEnd > getContentView().getRight() - DimensionUnit.DP.toPixels(20)) {
shapeableBubbleBackground.setShapeAppearanceModel(shapeAppearanceModel.setTopRightCornerSize(DimensionUnit.DP.toPixels(9f)).build());
bubble.setBackground(shapeableBubbleBackground);
} else if (arrowEnd < DimensionUnit.DP.toPixels(20)) {
shapeableBubbleBackground.setShapeAppearanceModel(shapeAppearanceModel.setTopLeftCornerSize(DimensionUnit.DP.toPixels(9f)).build());
bubble.setBackground(shapeableBubbleBackground);
}
});
float arrowEnd = arrow.getX() + arrow.getRight();
if (arrowEnd > getContentView().getRight() - DimensionUnit.DP.toPixels(20)) {
shapeableBubbleBackground.setShapeAppearanceModel(shapeAppearanceModel.setTopRightCornerSize(DimensionUnit.DP.toPixels(9f)).build());
bubble.setBackground(shapeableBubbleBackground);
} else if (arrowEnd < DimensionUnit.DP.toPixels(20)) {
shapeableBubbleBackground.setShapeAppearanceModel(shapeAppearanceModel.setTopLeftCornerSize(DimensionUnit.DP.toPixels(9f)).build());
bubble.setBackground(shapeableBubbleBackground);
}
});
}
try {
showAsDropDown(anchor, xoffset, yoffset);

View File

@@ -74,6 +74,8 @@ public class CallParticipantView extends ConstraintLayout {
private EmojiTextView infoMessage;
private Button infoMoreInfo;
private AppCompatImageView infoIcon;
private View switchCameraIconFrame;
private View switchCameraIcon;
public CallParticipantView(@NonNull Context context) {
super(context);
@@ -92,18 +94,20 @@ public class CallParticipantView extends ConstraintLayout {
protected void onFinishInflate() {
super.onFinishInflate();
backgroundAvatar = findViewById(R.id.call_participant_background_avatar);
avatar = findViewById(R.id.call_participant_item_avatar);
pipAvatar = findViewById(R.id.call_participant_item_pip_avatar);
rendererFrame = findViewById(R.id.call_participant_renderer_frame);
renderer = findViewById(R.id.call_participant_renderer);
audioIndicator = findViewById(R.id.call_participant_audio_indicator);
infoOverlay = findViewById(R.id.call_participant_info_overlay);
infoIcon = findViewById(R.id.call_participant_info_icon);
infoMessage = findViewById(R.id.call_participant_info_message);
infoMoreInfo = findViewById(R.id.call_participant_info_more_info);
badge = findViewById(R.id.call_participant_item_badge);
pipBadge = findViewById(R.id.call_participant_item_pip_badge);
backgroundAvatar = findViewById(R.id.call_participant_background_avatar);
avatar = findViewById(R.id.call_participant_item_avatar);
pipAvatar = findViewById(R.id.call_participant_item_pip_avatar);
rendererFrame = findViewById(R.id.call_participant_renderer_frame);
renderer = findViewById(R.id.call_participant_renderer);
audioIndicator = findViewById(R.id.call_participant_audio_indicator);
infoOverlay = findViewById(R.id.call_participant_info_overlay);
infoIcon = findViewById(R.id.call_participant_info_icon);
infoMessage = findViewById(R.id.call_participant_info_message);
infoMoreInfo = findViewById(R.id.call_participant_info_more_info);
badge = findViewById(R.id.call_participant_item_badge);
pipBadge = findViewById(R.id.call_participant_item_pip_badge);
switchCameraIconFrame = findViewById(R.id.call_participant_switch_camera);
switchCameraIcon = findViewById(R.id.call_participant_switch_camera_icon);
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
useLargeAvatar();
@@ -249,6 +253,27 @@ public class CallParticipantView extends ConstraintLayout {
ConstraintSet.BOTTOM,
ViewUtil.dpToPx(6)
);
constraints.setVisibility(R.id.call_participant_switch_camera, View.VISIBLE);
constraints.setMargin(
R.id.call_participant_switch_camera,
ConstraintSet.END,
ViewUtil.dpToPx(6)
);
constraints.setMargin(
R.id.call_participant_switch_camera,
ConstraintSet.BOTTOM,
ViewUtil.dpToPx(6)
);
constraints.constrainWidth(R.id.call_participant_switch_camera, ViewUtil.dpToPx(28));
constraints.constrainHeight(R.id.call_participant_switch_camera, ViewUtil.dpToPx(28));
ViewGroup.LayoutParams params = switchCameraIcon.getLayoutParams();
params.width = params.height = ViewUtil.dpToPx(16);
switchCameraIcon.setLayoutParams(params);
switchCameraIconFrame.setClickable(false);
switchCameraIconFrame.setEnabled(false);
}
case EXPANDED_SELF_PIP -> {
constraints.connect(
@@ -267,6 +292,27 @@ public class CallParticipantView extends ConstraintLayout {
ConstraintSet.BOTTOM,
ViewUtil.dpToPx(8)
);
constraints.setVisibility(R.id.call_participant_switch_camera, View.VISIBLE);
constraints.setMargin(
R.id.call_participant_switch_camera,
ConstraintSet.END,
ViewUtil.dpToPx(8)
);
constraints.setMargin(
R.id.call_participant_switch_camera,
ConstraintSet.BOTTOM,
ViewUtil.dpToPx(8)
);
constraints.constrainWidth(R.id.call_participant_switch_camera, ViewUtil.dpToPx(48));
constraints.constrainHeight(R.id.call_participant_switch_camera, ViewUtil.dpToPx(48));
ViewGroup.LayoutParams params = switchCameraIcon.getLayoutParams();
params.width = params.height = ViewUtil.dpToPx(24);
switchCameraIcon.setLayoutParams(params);
switchCameraIconFrame.setClickable(true);
switchCameraIconFrame.setEnabled(true);
}
case MINI_SELF_PIP -> {
constraints.connect(
@@ -288,6 +334,7 @@ public class CallParticipantView extends ConstraintLayout {
ConstraintSet.BOTTOM,
ViewUtil.dpToPx(6)
);
constraints.setVisibility(R.id.call_participant_switch_camera, View.GONE);
}
}

View File

@@ -278,6 +278,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
});
cameraDirectionToggle.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onCameraDirectionChanged));
smallLocalRender.findViewById(R.id.call_participant_switch_camera).setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onCameraDirectionChanged));
overflow.setOnClickListener(v -> {
runIfNonNull(controlsListener, ControlsListener::onOverflowClicked);
@@ -794,6 +795,10 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
return videoToggle;
}
public @NonNull View getSwitchCameraTooltipTarget() {
return smallLocalRenderFrame;
}
public void showSpeakerViewHint() {
groupCallSpeakerHint.get().setVisibility(View.VISIBLE);
}

View File

@@ -81,17 +81,18 @@ public class WebRtcCallViewModel extends ViewModel {
private final Runnable elapsedTimeRunnable = this::handleTick;
private final Runnable stopOutgoingRingingMode = this::stopOutgoingRingingMode;
private boolean canDisplayTooltipIfNeeded = true;
private boolean canDisplayPopupIfNeeded = true;
private boolean hasEnabledLocalVideo = false;
private boolean wasInOutgoingRingingMode = false;
private long callConnectedTime = -1;
private boolean answerWithVideoAvailable = false;
private boolean canEnterPipMode = false;
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
private boolean callStarting = false;
private boolean switchOnFirstScreenShare = true;
private boolean showScreenShareTip = true;
private boolean canDisplayTooltipIfNeeded = true;
private boolean canDisplaySwitchCameraTooltipIfNeeded = true;
private boolean canDisplayPopupIfNeeded = true;
private boolean hasEnabledLocalVideo = false;
private boolean wasInOutgoingRingingMode = false;
private long callConnectedTime = -1;
private boolean answerWithVideoAvailable = false;
private boolean canEnterPipMode = false;
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
private boolean callStarting = false;
private boolean switchOnFirstScreenShare = true;
private boolean showScreenShareTip = true;
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
@@ -261,6 +262,11 @@ public class WebRtcCallViewModel extends ViewModel {
canDisplayTooltipIfNeeded = false;
}
public void onDismissedSwitchCameraTooltip() {
canDisplaySwitchCameraTooltipIfNeeded = false;
SignalStore.tooltips().markCallingSwitchCameraTooltipSeen();
}
@MainThread
public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel, boolean enableVideo) {
canEnterPipMode = !webRtcViewModel.getState().isPreJoinOrNetworkUnavailable();
@@ -342,6 +348,16 @@ public class WebRtcCallViewModel extends ViewModel {
} else if (!webRtcViewModel.isCellularConnection()) {
canDisplayPopupIfNeeded = true;
}
if (SignalStore.tooltips().showCallingSwitchCameraTooltip() &&
canDisplaySwitchCameraTooltipIfNeeded &&
hasEnabledLocalVideo &&
webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED &&
!newState.getAllRemoteParticipants().isEmpty()
) {
canDisplaySwitchCameraTooltipIfNeeded = false;
events.setValue(new Event.ShowSwitchCameraTooltip());
}
}
@MainThread
@@ -537,6 +553,12 @@ public class WebRtcCallViewModel extends ViewModel {
public static class ShowWifiToCellularPopup extends Event {
}
public static class ShowSwitchCameraTooltip extends Event {
}
public static class DismissSwitchCameraTooltip extends Event {
}
public static class StartCall extends Event {
private final boolean isVideoCall;

View File

@@ -157,7 +157,7 @@ public final class WebRtcControls {
}
public boolean displayOverflow() {
return FeatureFlags.groupCallReactions() && isAtLeastOutgoing();
return FeatureFlags.groupCallReactions() && isAtLeastOutgoing() && hasAtLeastOneRemote && isGroupCall();
}
public boolean displayMuteAudio() {
@@ -173,7 +173,7 @@ public final class WebRtcControls {
}
public boolean displayCameraToggle() {
return (isPreJoin() || isAtLeastOutgoing()) && isLocalVideoEnabled && isMoreThanOneCameraAvailable;
return (isPreJoin() || (isAtLeastOutgoing() && !hasAtLeastOneRemote)) && isLocalVideoEnabled && isMoreThanOneCameraAvailable;
}
public boolean displayRemoteVideoRecycler() {

View File

@@ -15,7 +15,7 @@ public class TooltipValues extends SignalStoreValues {
private static final String MULTI_FORWARD_DIALOG = "tooltip.multi.forward.dialog";
private static final String BUBBLE_OPT_OUT = "tooltip.bubble.opt.out";
private static final String PROFILE_SETTINGS_QR_CODE = "tooltip.profile_settings_qr_code";
private static final String CALLING_SWITCH_CAMERA = "tooltip.calling.switch_camera";
TooltipValues(@NonNull KeyValueStore store) {
super(store);
@@ -82,4 +82,12 @@ public class TooltipValues extends SignalStoreValues {
public void markProfileSettingsQrCodeTooltipSeen() {
putBoolean(PROFILE_SETTINGS_QR_CODE, false);
}
public boolean showCallingSwitchCameraTooltip() {
return getBoolean(CALLING_SWITCH_CAMERA, true);
}
public void markCallingSwitchCameraTooltipSeen() {
putBoolean(CALLING_SWITCH_CAMERA, false);
}
}

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.webrtc.CallParticipantView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_height="match_parent"
tools:layout_width="match_parent">
tools:layout_width="match_parent"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_participant_background_avatar"
@@ -93,6 +93,29 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="@+id/call_participant_switch_camera"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="6dp"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/signal_dark_colorSecondaryContainer"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_participant_switch_camera_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center"
app:srcCompat="@drawable/symbol_switch_24"
app:tint="@color/core_white" />
</FrameLayout>
<LinearLayout
android:id="@+id/call_participant_info_overlay"
android:layout_width="0dp"

View File

@@ -2387,6 +2387,8 @@
<string name="WebRtcCallActivity__answered_on_a_linked_device">Answered on a linked device.</string>
<string name="WebRtcCallActivity__declined_on_a_linked_device">Declined on a linked device.</string>
<string name="WebRtcCallActivity__busy_on_a_linked_device">Busy on a linked device.</string>
<!-- Tooltip message shown first time user is in a video call after switch camera button moved -->
<string name="WebRtcCallActivity__flip_camera_tooltip">Flip Camera has been moved here, tap your video to try it out</string>
<string name="GroupCallSafetyNumberChangeNotification__someone_has_joined_this_call_with_a_safety_number_that_has_changed">Someone has joined this call with a safety number that has changed.</string>