kopia lustrzana https://github.com/ryukoposting/Signal-Android
Implement initial support for foldables in calling.
rodzic
927b6096c6
commit
5229e24397
|
@ -376,6 +376,7 @@ dependencies {
|
|||
implementation ('androidx.appcompat:appcompat:1.2.0') {
|
||||
force = true
|
||||
}
|
||||
implementation "androidx.window:window:1.0.0-alpha09"
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||
|
|
|
@ -9,3 +9,5 @@
|
|||
|
||||
# Protobuf lite
|
||||
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
|
||||
|
||||
-keep class androidx.window.** { *; }
|
|
@ -96,6 +96,7 @@
|
|||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
tools:replace="android:allowBackup"
|
||||
android:resizeableActivity="true"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:largeHeap="true">
|
||||
|
@ -104,6 +105,9 @@
|
|||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
|
||||
|
||||
<meta-data android:name="android.supports_size_changes"
|
||||
android:value="true" />
|
||||
|
||||
<meta-data android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
|
||||
|
@ -117,16 +121,15 @@
|
|||
<activity android:name=".WebRtcCallActivity"
|
||||
android:theme="@style/TextSecure.DarkTheme.WebRTCCall"
|
||||
android:excludeFromRecents="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
android:taskAffinity=".calling"
|
||||
android:resizeableActivity="true"
|
||||
android:launchMode="singleTask"/>
|
||||
|
||||
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:screenOrientation="portrait"
|
||||
android:noHistory="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
|
|
|
@ -22,8 +22,10 @@ import android.annotation.SuppressLint;
|
|||
import android.app.PictureInPictureParams;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -35,11 +37,16 @@ import androidx.annotation.NonNull;
|
|||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.window.DisplayFeature;
|
||||
import androidx.window.FoldingFeature;
|
||||
import androidx.window.WindowLayoutInfo;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
|
@ -50,6 +57,7 @@ import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeN
|
|||
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls;
|
||||
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
@ -70,6 +78,7 @@ import org.whispersystems.libsignal.util.Pair;
|
|||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||
|
||||
|
@ -88,11 +97,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||
private DeviceOrientationMonitor deviceOrientationMonitor;
|
||||
|
||||
private FullscreenHelper fullscreenHelper;
|
||||
private WebRtcCallView callScreen;
|
||||
private TooltipPopup videoTooltip;
|
||||
private WebRtcCallViewModel viewModel;
|
||||
private boolean enableVideoIfAvailable;
|
||||
private FullscreenHelper fullscreenHelper;
|
||||
private WebRtcCallView callScreen;
|
||||
private TooltipPopup videoTooltip;
|
||||
private WebRtcCallViewModel viewModel;
|
||||
private boolean enableVideoIfAvailable;
|
||||
private androidx.window.WindowManager windowManager;
|
||||
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(@NonNull Context newBase) {
|
||||
|
@ -100,6 +111,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
super.attachBaseContext(newBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (newConfig.densityDpi >= 480 && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
||||
viewModel.setIsLandscapeEnabled(true);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
} else if (newConfig.densityDpi < 480){
|
||||
viewModel.setIsLandscapeEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
|
@ -107,6 +131,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
boolean isLandscapeEnabled = getResources().getConfiguration().densityDpi >= 480;
|
||||
if (!isLandscapeEnabled) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.webrtc_call_activity);
|
||||
|
||||
|
@ -115,12 +144,17 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
|
||||
|
||||
initializeResources();
|
||||
initializeViewModel();
|
||||
initializeViewModel(isLandscapeEnabled);
|
||||
|
||||
processIntent(getIntent());
|
||||
|
||||
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
|
||||
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
|
||||
|
||||
windowManager = new androidx.window.WindowManager(this);
|
||||
windowLayoutInfoConsumer = new WindowLayoutInfoConsumer(windowManager);
|
||||
|
||||
windowManager.registerLayoutChangeCallback(SignalExecutors.BOUNDED, windowLayoutInfoConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -178,6 +212,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
windowManager.unregisterLayoutChangeCallback(windowLayoutInfoConsumer);
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
|
@ -208,8 +243,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
private boolean enterPipModeIfPossible() {
|
||||
if (viewModel.canEnterPipMode() && isSystemPipEnabledAndAvailable()) {
|
||||
PictureInPictureParams params = new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(9, 16))
|
||||
.build();
|
||||
.setAspectRatio(new Rational(9, 16))
|
||||
.build();
|
||||
enterPictureInPictureMode(params);
|
||||
CallParticipantsListDialog.dismiss(getSupportFragmentManager());
|
||||
|
||||
|
@ -248,13 +283,14 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
private void initializeViewModel(boolean isLandscapeEnabled) {
|
||||
deviceOrientationMonitor = new DeviceOrientationMonitor(this);
|
||||
getLifecycle().addObserver(deviceOrientationMonitor);
|
||||
|
||||
WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor);
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class);
|
||||
viewModel.setIsLandscapeEnabled(isLandscapeEnabled);
|
||||
viewModel.setIsInPipMode(isInPipMode());
|
||||
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
|
||||
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
|
||||
|
@ -276,25 +312,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
}
|
||||
});
|
||||
|
||||
viewModel.getOrientation().observe(this, orientation -> {
|
||||
ApplicationDependencies.getSignalCallManager().orientationChanged(orientation.getDegrees());
|
||||
|
||||
switch (orientation) {
|
||||
case LANDSCAPE_LEFT_EDGE:
|
||||
callScreen.rotateControls(90);
|
||||
break;
|
||||
case LANDSCAPE_RIGHT_EDGE:
|
||||
callScreen.rotateControls(-90);
|
||||
break;
|
||||
case PORTRAIT_BOTTOM_EDGE:
|
||||
callScreen.rotateControls(0);
|
||||
}
|
||||
});
|
||||
viewModel.getOrientationAndLandscapeEnabled().observe(this, pair -> ApplicationDependencies.getSignalCallManager().orientationChanged(pair.second, pair.first.getDegrees()));
|
||||
viewModel.getControlsRotation().observe(this, callScreen::rotateControls);
|
||||
}
|
||||
|
||||
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
||||
if (event instanceof WebRtcCallViewModel.Event.StartCall) {
|
||||
startCall(((WebRtcCallViewModel.Event.StartCall)event).isVideoCall());
|
||||
startCall(((WebRtcCallViewModel.Event.StartCall) event).isVideoCall());
|
||||
return;
|
||||
} else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) {
|
||||
SafetyNumberChangeDialog.showForGroupCall(getSupportFragmentManager(), ((WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) event).getIdentityRecords());
|
||||
|
@ -406,7 +430,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)),
|
||||
R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
|
||||
R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
|
||||
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
|
||||
.onAllGranted(() -> {
|
||||
callScreen.setRecipient(recipient);
|
||||
|
@ -490,13 +514,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
|
||||
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.RedPhone_number_not_registered)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
||||
.setOnCancelListener(d -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
||||
.show();
|
||||
.setTitle(R.string.RedPhone_number_not_registered)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
||||
.setOnCancelListener(d -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
|
||||
|
@ -588,20 +612,34 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
callScreen.setRecipient(event.getRecipient());
|
||||
|
||||
switch (event.getState()) {
|
||||
case CALL_PRE_JOIN: handleCallPreJoin(event); break;
|
||||
case CALL_CONNECTED: handleCallConnected(event); break;
|
||||
case NETWORK_FAILURE: handleServerFailure(); break;
|
||||
case CALL_RINGING: handleCallRinging(); break;
|
||||
case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
|
||||
case CALL_ACCEPTED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break;
|
||||
case CALL_DECLINED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.DECLINED); break;
|
||||
case CALL_ONGOING_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.BUSY); break;
|
||||
case CALL_NEEDS_PERMISSION: handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
|
||||
case NO_SUCH_USER: handleNoSuchUser(event); break;
|
||||
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(); break;
|
||||
case CALL_OUTGOING: handleOutgoingCall(event); break;
|
||||
case CALL_BUSY: handleCallBusy(); break;
|
||||
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
|
||||
case CALL_PRE_JOIN:
|
||||
handleCallPreJoin(event); break;
|
||||
case CALL_CONNECTED:
|
||||
handleCallConnected(event); break;
|
||||
case NETWORK_FAILURE:
|
||||
handleServerFailure(); break;
|
||||
case CALL_RINGING:
|
||||
handleCallRinging(); break;
|
||||
case CALL_DISCONNECTED:
|
||||
handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
|
||||
case CALL_ACCEPTED_ELSEWHERE:
|
||||
handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break;
|
||||
case CALL_DECLINED_ELSEWHERE:
|
||||
handleTerminate(event.getRecipient(), HangupMessage.Type.DECLINED); break;
|
||||
case CALL_ONGOING_ELSEWHERE:
|
||||
handleTerminate(event.getRecipient(), HangupMessage.Type.BUSY); break;
|
||||
case CALL_NEEDS_PERMISSION:
|
||||
handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
|
||||
case NO_SUCH_USER:
|
||||
handleNoSuchUser(event); break;
|
||||
case RECIPIENT_UNAVAILABLE:
|
||||
handleRecipientUnavailable(); break;
|
||||
case CALL_OUTGOING:
|
||||
handleOutgoingCall(event); break;
|
||||
case CALL_BUSY:
|
||||
handleCallBusy(); break;
|
||||
case UNTRUSTED_IDENTITY:
|
||||
handleUntrustedIdentity(event); break;
|
||||
}
|
||||
|
||||
boolean enableVideo = event.getLocalParticipant().getCameraState().getCameraCount() > 0 && enableVideoIfAvailable;
|
||||
|
@ -732,4 +770,31 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
viewModel.onLocalPictureInPictureClicked();
|
||||
}
|
||||
}
|
||||
|
||||
private class WindowLayoutInfoConsumer implements Consumer<WindowLayoutInfo> {
|
||||
|
||||
private final androidx.window.WindowManager windowManager;
|
||||
|
||||
private WindowLayoutInfoConsumer(androidx.window.WindowManager windowManager) {
|
||||
this.windowManager = windowManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(WindowLayoutInfo windowLayoutInfo) {
|
||||
Log.d(TAG, "On WindowLayoutInfo accepted: " + windowLayoutInfo.toString());
|
||||
|
||||
Optional<DisplayFeature> feature = windowLayoutInfo.getDisplayFeatures().stream().filter(f -> f instanceof FoldingFeature).findFirst();
|
||||
if (feature.isPresent()) {
|
||||
FoldingFeature foldingFeature = (FoldingFeature) feature.get();
|
||||
Rect bounds = foldingFeature.getBounds();
|
||||
if (foldingFeature.getState() == FoldingFeature.State.HALF_OPENED && bounds.top == bounds.bottom) {
|
||||
Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in table-top display mode");
|
||||
viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top));
|
||||
} else {
|
||||
Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in flat display mode");
|
||||
viewModel.setFoldableState(WebRtcControls.FoldableState.flat());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.util.WeakHashMap;
|
|||
*/
|
||||
public class BroadcastVideoSink implements VideoSink {
|
||||
|
||||
public static final int DEVICE_ROTATION_IGNORE = -1;
|
||||
|
||||
private final EglBase eglBase;
|
||||
private final WeakHashMap<VideoSink, Boolean> sinks;
|
||||
private final WeakHashMap<Object, Point> requestingSizes;
|
||||
|
@ -85,7 +87,10 @@ public class BroadcastVideoSink implements VideoSink {
|
|||
|
||||
@Override
|
||||
public synchronized void onFrame(@NonNull VideoFrame videoFrame) {
|
||||
if (videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth() || forceRotate) {
|
||||
boolean isDeviceRotationIgnored = deviceOrientationDegrees == DEVICE_ROTATION_IGNORE;
|
||||
boolean isWideVideoFrame = videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth();
|
||||
|
||||
if (!isDeviceRotationIgnored && (isWideVideoFrame || forceRotate)) {
|
||||
int rotation = calculateRotation();
|
||||
if (rotation > 0) {
|
||||
rotation += rotateWithDevice ? videoFrame.getRotation() : 0;
|
||||
|
|
|
@ -34,6 +34,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
|||
private CallParticipant focusedParticipant = null;
|
||||
private boolean shouldRenderInPip;
|
||||
private boolean isPortrait;
|
||||
private LayoutStrategy layoutStrategy;
|
||||
|
||||
public CallParticipantsLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
|
@ -47,11 +48,18 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
|||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
void update(@NonNull List<CallParticipant> callParticipants, @NonNull CallParticipant focusedParticipant, boolean shouldRenderInPip, boolean isPortrait) {
|
||||
void update(@NonNull List<CallParticipant> callParticipants,
|
||||
@NonNull CallParticipant focusedParticipant,
|
||||
boolean shouldRenderInPip,
|
||||
boolean isPortrait,
|
||||
@NonNull LayoutStrategy layoutStrategy)
|
||||
{
|
||||
this.callParticipants = callParticipants;
|
||||
this.focusedParticipant = focusedParticipant;
|
||||
this.shouldRenderInPip = shouldRenderInPip;
|
||||
this.isPortrait = isPortrait;
|
||||
this.layoutStrategy = layoutStrategy;
|
||||
setFlexDirection(layoutStrategy.getFlexDirection());
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
|
@ -127,7 +135,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
|||
callParticipantView.useLargeAvatar();
|
||||
}
|
||||
|
||||
setChildLayoutParams(view, index, getChildCount());
|
||||
layoutStrategy.setChildLayoutParams(view, index, getChildCount());
|
||||
}
|
||||
|
||||
private void addCallParticipantView() {
|
||||
|
@ -139,17 +147,9 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
|||
addView(view);
|
||||
}
|
||||
|
||||
private void setChildLayoutParams(@NonNull View child, int childPosition, int childCount) {
|
||||
FlexboxLayout.LayoutParams params = (FlexboxLayout.LayoutParams) child.getLayoutParams();
|
||||
if (childCount < 3) {
|
||||
params.setFlexBasisPercent(1f);
|
||||
} else {
|
||||
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
|
||||
params.setFlexBasisPercent(1f);
|
||||
} else {
|
||||
params.setFlexBasisPercent(0.5f);
|
||||
}
|
||||
}
|
||||
child.setLayoutParams(params);
|
||||
public interface LayoutStrategy {
|
||||
int getFlexDirection();
|
||||
|
||||
void setChildLayoutParams(@NonNull View child, int childPosition, int childCount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package org.thoughtcrime.securesms.components.webrtc
|
||||
|
||||
import android.view.View
|
||||
import com.google.android.flexbox.FlexDirection
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
|
||||
object CallParticipantsLayoutStrategies {
|
||||
|
||||
private object Portrait : CallParticipantsLayout.LayoutStrategy {
|
||||
override fun getFlexDirection(): Int = FlexDirection.ROW
|
||||
|
||||
override fun setChildLayoutParams(child: View, childPosition: Int, childCount: Int) {
|
||||
val params = child.layoutParams as FlexboxLayout.LayoutParams
|
||||
if (childCount < 3) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
params.flexBasisPercent = 0.5f
|
||||
}
|
||||
}
|
||||
child.layoutParams = params
|
||||
}
|
||||
}
|
||||
|
||||
private object Landscape : CallParticipantsLayout.LayoutStrategy {
|
||||
override fun getFlexDirection() = FlexDirection.COLUMN
|
||||
|
||||
override fun setChildLayoutParams(child: View, childPosition: Int, childCount: Int) {
|
||||
val params = child.layoutParams as FlexboxLayout.LayoutParams
|
||||
if (childCount < 4) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
params.flexBasisPercent = 0.5f
|
||||
}
|
||||
}
|
||||
child.layoutParams = params
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getStrategy(isPortrait: Boolean): CallParticipantsLayout.LayoutStrategy {
|
||||
return if (isPortrait) {
|
||||
Portrait
|
||||
} else {
|
||||
Landscape
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,8 @@ public final class CallParticipantsState {
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
OptionalLong.empty());
|
||||
OptionalLong.empty(),
|
||||
WebRtcControls.FoldableState.flat());
|
||||
|
||||
private final WebRtcViewModel.State callState;
|
||||
private final WebRtcViewModel.GroupCallState groupCallState;
|
||||
|
@ -48,6 +49,7 @@ public final class CallParticipantsState {
|
|||
private final boolean showVideoForOutgoing;
|
||||
private final boolean isViewingFocusedParticipant;
|
||||
private final OptionalLong remoteDevicesCount;
|
||||
private final WebRtcControls.FoldableState foldableState;
|
||||
|
||||
public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
|
||||
@NonNull WebRtcViewModel.GroupCallState groupCallState,
|
||||
|
@ -58,7 +60,8 @@ public final class CallParticipantsState {
|
|||
boolean isInPipMode,
|
||||
boolean showVideoForOutgoing,
|
||||
boolean isViewingFocusedParticipant,
|
||||
OptionalLong remoteDevicesCount)
|
||||
OptionalLong remoteDevicesCount,
|
||||
@NonNull WebRtcControls.FoldableState foldableState)
|
||||
{
|
||||
this.callState = callState;
|
||||
this.groupCallState = groupCallState;
|
||||
|
@ -70,6 +73,7 @@ public final class CallParticipantsState {
|
|||
this.showVideoForOutgoing = showVideoForOutgoing;
|
||||
this.isViewingFocusedParticipant = isViewingFocusedParticipant;
|
||||
this.remoteDevicesCount = remoteDevicesCount;
|
||||
this.foldableState = foldableState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcViewModel.State getCallState() {
|
||||
|
@ -94,7 +98,10 @@ public final class CallParticipantsState {
|
|||
listParticipants.addAll(remoteParticipants.getListParticipants());
|
||||
}
|
||||
|
||||
listParticipants.add(CallParticipant.EMPTY);
|
||||
if (foldableState.isFlat()) {
|
||||
listParticipants.add(CallParticipant.EMPTY);
|
||||
}
|
||||
|
||||
Collections.reverse(listParticipants);
|
||||
|
||||
return listParticipants;
|
||||
|
@ -155,6 +162,10 @@ public final class CallParticipantsState {
|
|||
return localRenderState;
|
||||
}
|
||||
|
||||
public boolean isFolded() {
|
||||
return foldableState.isFolded();
|
||||
}
|
||||
|
||||
public boolean isLargeVideoGroup() {
|
||||
return getAllRemoteParticipants().size() > SMALL_GROUP_MAX;
|
||||
}
|
||||
|
@ -215,7 +226,8 @@ public final class CallParticipantsState {
|
|||
oldState.isInPipMode,
|
||||
newShowVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
webRtcViewModel.getRemoteDevicesCount());
|
||||
webRtcViewModel.getRemoteDevicesCount(),
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, boolean isInPip) {
|
||||
|
@ -237,7 +249,8 @@ public final class CallParticipantsState {
|
|||
isInPip,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount);
|
||||
oldState.remoteDevicesCount,
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState setExpanded(@NonNull CallParticipantsState oldState, boolean expanded) {
|
||||
|
@ -259,7 +272,8 @@ public final class CallParticipantsState {
|
|||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount);
|
||||
oldState.remoteDevicesCount,
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
|
||||
|
@ -281,7 +295,31 @@ public final class CallParticipantsState {
|
|||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
selectedPage == SelectedPage.FOCUSED,
|
||||
oldState.remoteDevicesCount);
|
||||
oldState.remoteDevicesCount,
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull WebRtcControls.FoldableState foldableState) {
|
||||
WebRtcLocalRenderState localRenderState = determineLocalRenderMode(oldState.localParticipant,
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.getGroupCallState().isNotIdle(),
|
||||
oldState.callState,
|
||||
oldState.getAllRemoteParticipants().size(),
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||
|
||||
return new CallParticipantsState(oldState.callState,
|
||||
oldState.groupCallState,
|
||||
oldState.remoteParticipants,
|
||||
oldState.localParticipant,
|
||||
oldState.focusedParticipant,
|
||||
localRenderState,
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount,
|
||||
foldableState);
|
||||
}
|
||||
|
||||
private static @NonNull WebRtcLocalRenderState determineLocalRenderMode(@NonNull CallParticipant localParticipant,
|
||||
|
|
|
@ -116,14 +116,16 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
|
|||
}
|
||||
|
||||
public void clearVerticalBoundaries() {
|
||||
setVerticalBoundaries(parent.getTop(), parent.getMeasuredHeight() + parent.getTop());
|
||||
setTopVerticalBoundary(parent.getTop());
|
||||
setBottomVerticalBoundary(parent.getMeasuredHeight() + parent.getTop());
|
||||
}
|
||||
|
||||
public void setVerticalBoundaries(int topBoundary, int bottomBoundary) {
|
||||
extraPaddingTop = topBoundary - parent.getTop();
|
||||
extraPaddingBottom = parent.getMeasuredHeight() + parent.getTop() - bottomBoundary;
|
||||
public void setTopVerticalBoundary(int topBoundary) {
|
||||
extraPaddingTop = topBoundary - parent.getTop();
|
||||
}
|
||||
|
||||
adjustPip();
|
||||
public void setBottomVerticalBoundary(int bottomBoundary) {
|
||||
extraPaddingBottom = parent.getMeasuredHeight() + parent.getTop() - bottomBoundary;
|
||||
}
|
||||
|
||||
private boolean onGestureFinished(MotionEvent e) {
|
||||
|
|
|
@ -64,6 +64,10 @@ class WebRtcCallParticipantsPage {
|
|||
return isPortrait;
|
||||
}
|
||||
|
||||
public @NonNull CallParticipantsLayout.LayoutStrategy getLayoutStrategy() {
|
||||
return CallParticipantsLayoutStrategies.getStrategy(isPortrait);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -86,7 +86,7 @@ class WebRtcCallParticipantsPagerAdapter extends ListAdapter<WebRtcCallParticipa
|
|||
|
||||
@Override
|
||||
void bind(WebRtcCallParticipantsPage page) {
|
||||
callParticipantsLayout.update(page.getCallParticipants(), page.getFocusedParticipant(), page.isRenderInPip(), page.isPortrait());
|
||||
callParticipantsLayout.update(page.getCallParticipants(), page.getFocusedParticipant(), page.isRenderInPip(), page.isPortrait(), page.getLayoutStrategy());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import com.google.android.material.button.MaterialButton;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
||||
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
|
@ -46,6 +45,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.util.BlurTransformation;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
@ -57,9 +57,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||
|
||||
public class WebRtcCallView extends FrameLayout {
|
||||
public class WebRtcCallView extends ConstraintLayout {
|
||||
|
||||
private static final long TRANSITION_DURATION_MILLIS = 250;
|
||||
private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8;
|
||||
|
@ -102,12 +100,15 @@ public class WebRtcCallView extends FrameLayout {
|
|||
private View errorButton;
|
||||
private int pagerBottomMarginDp;
|
||||
private boolean controlsVisible = true;
|
||||
private Guideline topFoldGuideline;
|
||||
private Guideline callScreenTopFoldGuideline;
|
||||
private View foldParticipantCountWrapper;
|
||||
private TextView foldParticipantCount;
|
||||
|
||||
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
||||
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
||||
private PictureInPictureExpansionHelper pictureInPictureExpansionHelper;
|
||||
|
||||
|
||||
private final Set<View> incomingCallViews = new HashSet<>();
|
||||
private final Set<View> topViews = new HashSet<>();
|
||||
private final Set<View> visibleViewSet = new HashSet<>();
|
||||
|
@ -161,6 +162,10 @@ public class WebRtcCallView extends FrameLayout {
|
|||
errorButton = findViewById(R.id.call_screen_error_cancel);
|
||||
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
|
||||
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
|
||||
topFoldGuideline = findViewById(R.id.fold_top_guideline);
|
||||
callScreenTopFoldGuideline = findViewById(R.id.fold_top_call_screen_guideline);
|
||||
foldParticipantCountWrapper = findViewById(R.id.fold_show_participants_menu_counter_wrapper);
|
||||
foldParticipantCount = findViewById(R.id.fold_show_participants_menu_counter);
|
||||
|
||||
View topGradient = findViewById(R.id.call_screen_header_gradient);
|
||||
View decline = findViewById(R.id.call_screen_decline_call);
|
||||
|
@ -279,10 +284,18 @@ public class WebRtcCallView extends FrameLayout {
|
|||
@Override
|
||||
public void onWindowSystemUiVisibilityChanged(int visible) {
|
||||
if ((visible & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
|
||||
pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop());
|
||||
if (controls.adjustForFold()) {
|
||||
pictureInPictureGestureHelper.clearVerticalBoundaries();
|
||||
pictureInPictureGestureHelper.setTopVerticalBoundary(toolbar.getBottom());
|
||||
} else {
|
||||
pictureInPictureGestureHelper.setTopVerticalBoundary(toolbar.getBottom());
|
||||
pictureInPictureGestureHelper.setBottomVerticalBoundary(videoToggle.getTop());
|
||||
}
|
||||
} else {
|
||||
pictureInPictureGestureHelper.clearVerticalBoundaries();
|
||||
}
|
||||
|
||||
pictureInPictureGestureHelper.adjustPip();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -323,16 +336,22 @@ public class WebRtcCallView extends FrameLayout {
|
|||
}
|
||||
|
||||
if (state.getGroupCallState().isNotIdle() && participantCount != null) {
|
||||
participantCount.setText(state.getParticipantCount()
|
||||
.mapToObj(String::valueOf).orElse("\u2014"));
|
||||
participantCount.setEnabled(state.getParticipantCount().isPresent());
|
||||
String text = state.getParticipantCount()
|
||||
.mapToObj(String::valueOf).orElse("\u2014");
|
||||
boolean enabled = state.getParticipantCount().isPresent();
|
||||
|
||||
participantCount.setText(text);
|
||||
participantCount.setEnabled(enabled);
|
||||
|
||||
foldParticipantCount.setText(text);
|
||||
foldParticipantCount.setEnabled(enabled);
|
||||
}
|
||||
|
||||
pagerAdapter.submitList(pages);
|
||||
recyclerAdapter.submitList(state.getListParticipants());
|
||||
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant());
|
||||
|
||||
if (state.isLargeVideoGroup() && !state.isInPipMode()) {
|
||||
if (state.isLargeVideoGroup() && !state.isInPipMode() && !state.isFolded()) {
|
||||
layoutParticipantsForLargeCount();
|
||||
} else {
|
||||
layoutParticipantsForSmallCount();
|
||||
|
@ -423,7 +442,9 @@ public class WebRtcCallView extends FrameLayout {
|
|||
toolbar.inflateMenu(R.menu.group_call);
|
||||
|
||||
View showParticipants = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list).getActionView();
|
||||
showParticipants.setAlpha(0);
|
||||
showParticipants.setOnClickListener(unused -> showParticipantsList());
|
||||
foldParticipantCountWrapper.setOnClickListener(unused -> showParticipantsList());
|
||||
|
||||
participantCount = showParticipants.findViewById(R.id.show_participants_menu_counter);
|
||||
}
|
||||
|
@ -480,6 +501,20 @@ public class WebRtcCallView extends FrameLayout {
|
|||
|
||||
visibleViewSet.clear();
|
||||
|
||||
if (webRtcControls.adjustForFold()) {
|
||||
topFoldGuideline.setGuidelineEnd(webRtcControls.getFold());
|
||||
callScreenTopFoldGuideline.setGuidelineEnd(webRtcControls.getFold());
|
||||
|
||||
if (webRtcControls.displayGroupMembersButton()) {
|
||||
visibleViewSet.add(foldParticipantCountWrapper);
|
||||
}
|
||||
} else {
|
||||
topFoldGuideline.setGuidelineEnd(0);
|
||||
callScreenTopFoldGuideline.setGuidelineEnd(0);
|
||||
}
|
||||
|
||||
setShowParticipantsState(webRtcControls.adjustForFold());
|
||||
|
||||
if (webRtcControls.displayStartCallControls()) {
|
||||
visibleViewSet.add(footerGradient);
|
||||
visibleViewSet.add(startCallControls);
|
||||
|
@ -577,10 +612,15 @@ public class WebRtcCallView extends FrameLayout {
|
|||
|
||||
controls = webRtcControls;
|
||||
|
||||
if (!controls.isFadeOutEnabled()) {
|
||||
controlsVisible = true;
|
||||
}
|
||||
|
||||
if (!visibleViewSet.equals(lastVisibleSet) || !controls.isFadeOutEnabled()) {
|
||||
fadeInNewUiState(lastVisibleSet, webRtcControls.displaySmallOngoingCallButtons());
|
||||
post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop()));
|
||||
}
|
||||
|
||||
onWindowSystemUiVisibilityChanged(getWindowSystemUiVisibility());
|
||||
}
|
||||
|
||||
public @NonNull View getVideoTooltipTarget() {
|
||||
|
@ -597,6 +637,18 @@ public class WebRtcCallView extends FrameLayout {
|
|||
}
|
||||
}
|
||||
|
||||
private void setShowParticipantsState(boolean isFolded) {
|
||||
MenuItem item = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list);
|
||||
|
||||
if (item != null) {
|
||||
View showParticipants = item.getActionView();
|
||||
showParticipants.animate().alpha(isFolded ? 0f : 1f);
|
||||
showParticipants.setClickable(!isFolded);
|
||||
foldParticipantCountWrapper.animate().alpha(isFolded ? 1f : 0f);
|
||||
foldParticipantCount.setClickable(isFolded);
|
||||
}
|
||||
}
|
||||
|
||||
private void expandPip(@NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||
pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
|
||||
@Override
|
||||
|
@ -760,6 +812,8 @@ public class WebRtcCallView extends FrameLayout {
|
|||
constraintSet.setVisibility(view.getId(), visibility);
|
||||
}
|
||||
|
||||
adjustParticipantsRecycler(constraintSet);
|
||||
|
||||
constraintSet.applyTo(parent);
|
||||
|
||||
layoutParticipants();
|
||||
|
@ -789,9 +843,21 @@ public class WebRtcCallView extends FrameLayout {
|
|||
}
|
||||
}
|
||||
|
||||
adjustParticipantsRecycler(constraintSet);
|
||||
|
||||
constraintSet.applyTo(parent);
|
||||
}
|
||||
|
||||
private void adjustParticipantsRecycler(@NonNull ConstraintSet constraintSet) {
|
||||
if (controlsVisible) {
|
||||
constraintSet.connect(R.id.call_screen_participants_recycler, ConstraintSet.BOTTOM, R.id.call_screen_video_toggle, ConstraintSet.TOP);
|
||||
} else {
|
||||
constraintSet.connect(R.id.call_screen_participants_recycler, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM);
|
||||
}
|
||||
|
||||
constraintSet.setHorizontalBias(R.id.call_screen_participants_recycler, controls.adjustForFold() ? 0.5f : 1f);
|
||||
}
|
||||
|
||||
private void scheduleFadeOut() {
|
||||
cancelFadeOut();
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.webrtc;
|
|||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -14,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider;
|
|||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
|
@ -41,7 +43,9 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
|
||||
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
|
||||
private final MutableLiveData<WebRtcControls.FoldableState> foldableState = new MutableLiveData<>(WebRtcControls.FoldableState.flat());
|
||||
private final LiveData<WebRtcControls> controlsWithFoldableState = LiveDataUtil.combineLatest(foldableState, webRtcControls, this::updateControlsFoldableState);
|
||||
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, controlsWithFoldableState, this::getRealWebRtcControls);
|
||||
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
|
||||
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
|
||||
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
|
||||
|
@ -53,6 +57,8 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1);
|
||||
private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
|
||||
private final LiveData<Orientation> orientation;
|
||||
private final MutableLiveData<Boolean> isLandscapeEnabled = new MutableLiveData<>();
|
||||
private final LiveData<Integer> controlsRotation;
|
||||
|
||||
private boolean canDisplayTooltipIfNeeded = true;
|
||||
private boolean hasEnabledLocalVideo = false;
|
||||
|
@ -69,13 +75,24 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
|
||||
|
||||
private WebRtcCallViewModel(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) {
|
||||
orientation = deviceOrientationMonitor.getOrientation();
|
||||
orientation = deviceOrientationMonitor.getOrientation();
|
||||
controlsRotation = LiveDataUtil.combineLatest(Transformations.distinctUntilChanged(isLandscapeEnabled),
|
||||
Transformations.distinctUntilChanged(orientation),
|
||||
this::resolveRotation);
|
||||
}
|
||||
|
||||
public LiveData<Integer> getControlsRotation() {
|
||||
return controlsRotation;
|
||||
}
|
||||
|
||||
public LiveData<Orientation> getOrientation() {
|
||||
return Transformations.distinctUntilChanged(orientation);
|
||||
}
|
||||
|
||||
public LiveData<Pair<Orientation, Boolean>> getOrientationAndLandscapeEnabled() {
|
||||
return LiveDataUtil.combineLatest(orientation, isLandscapeEnabled, Pair::new);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getMicrophoneEnabled() {
|
||||
return Transformations.distinctUntilChanged(microphoneEnabled);
|
||||
}
|
||||
|
@ -92,6 +109,12 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
liveRecipient.setValue(recipient.live());
|
||||
}
|
||||
|
||||
public void setFoldableState(@NonNull WebRtcControls.FoldableState foldableState) {
|
||||
this.foldableState.postValue(foldableState);
|
||||
|
||||
ThreadUtil.runOnMain(() -> participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), foldableState)));
|
||||
}
|
||||
|
||||
public LiveData<Event> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
@ -140,6 +163,10 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
|
||||
}
|
||||
|
||||
public void setIsLandscapeEnabled(boolean isLandscapeEnabled) {
|
||||
this.isLandscapeEnabled.postValue(isLandscapeEnabled);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setIsViewingFocusedParticipant(@NonNull CallParticipantsState.SelectedPage page) {
|
||||
if (page == CallParticipantsState.SelectedPage.FOCUSED) {
|
||||
|
@ -239,6 +266,23 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
private int resolveRotation(boolean isLandscapeEnabled, @NonNull Orientation orientation) {
|
||||
if (isLandscapeEnabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (orientation) {
|
||||
case LANDSCAPE_LEFT_EDGE:
|
||||
return 90;
|
||||
case LANDSCAPE_RIGHT_EDGE:
|
||||
return -90;
|
||||
case PORTRAIT_BOTTOM_EDGE:
|
||||
return 0;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsPlaceholders(@NonNull List<CallParticipant> callParticipants) {
|
||||
return Stream.of(callParticipants).anyMatch(p -> p.getCallParticipantId().getDemuxId() == CallParticipantId.DEFAULT_ID);
|
||||
}
|
||||
|
@ -314,7 +358,12 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
callState,
|
||||
groupCallState,
|
||||
audioOutput,
|
||||
participantLimit));
|
||||
participantLimit,
|
||||
WebRtcControls.FoldableState.flat()));
|
||||
}
|
||||
|
||||
private @NonNull WebRtcControls updateControlsFoldableState(@NonNull WebRtcControls.FoldableState foldableState, @NonNull WebRtcControls controls) {
|
||||
return controls.withFoldableState(foldableState);
|
||||
}
|
||||
|
||||
private @NonNull WebRtcControls getRealWebRtcControls(boolean isInPipMode, @NonNull WebRtcControls controls) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
@ -11,7 +12,7 @@ import org.thoughtcrime.securesms.R;
|
|||
public final class WebRtcControls {
|
||||
|
||||
public static final WebRtcControls NONE = new WebRtcControls();
|
||||
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null);
|
||||
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null, FoldableState.flat());
|
||||
|
||||
private final boolean isRemoteVideoEnabled;
|
||||
private final boolean isLocalVideoEnabled;
|
||||
|
@ -23,9 +24,10 @@ public final class WebRtcControls {
|
|||
private final GroupCallState groupCallState;
|
||||
private final WebRtcAudioOutput audioOutput;
|
||||
private final Long participantLimit;
|
||||
private final FoldableState foldableState;
|
||||
|
||||
private WebRtcControls() {
|
||||
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null);
|
||||
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null, FoldableState.flat());
|
||||
}
|
||||
|
||||
WebRtcControls(boolean isLocalVideoEnabled,
|
||||
|
@ -37,7 +39,8 @@ public final class WebRtcControls {
|
|||
@NonNull CallState callState,
|
||||
@NonNull GroupCallState groupCallState,
|
||||
@NonNull WebRtcAudioOutput audioOutput,
|
||||
@Nullable Long participantLimit)
|
||||
@Nullable Long participantLimit,
|
||||
@NonNull FoldableState foldableState)
|
||||
{
|
||||
this.isLocalVideoEnabled = isLocalVideoEnabled;
|
||||
this.isRemoteVideoEnabled = isRemoteVideoEnabled;
|
||||
|
@ -49,6 +52,21 @@ public final class WebRtcControls {
|
|||
this.groupCallState = groupCallState;
|
||||
this.audioOutput = audioOutput;
|
||||
this.participantLimit = participantLimit;
|
||||
this.foldableState = foldableState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcControls withFoldableState(FoldableState foldableState) {
|
||||
return new WebRtcControls(isLocalVideoEnabled,
|
||||
isRemoteVideoEnabled,
|
||||
isMoreThanOneCameraAvailable,
|
||||
isBluetoothAvailable,
|
||||
isInPipMode,
|
||||
hasAtLeastOneRemote,
|
||||
callState,
|
||||
groupCallState,
|
||||
audioOutput,
|
||||
participantLimit,
|
||||
foldableState);
|
||||
}
|
||||
|
||||
boolean displayErrorControls() {
|
||||
|
@ -59,6 +77,14 @@ public final class WebRtcControls {
|
|||
return isPreJoin();
|
||||
}
|
||||
|
||||
boolean adjustForFold() {
|
||||
return foldableState.isFolded();
|
||||
}
|
||||
|
||||
@Px int getFold() {
|
||||
return foldableState.getFoldPoint();
|
||||
}
|
||||
|
||||
@StringRes int getStartCallButtonText() {
|
||||
if (isGroupCall()) {
|
||||
if (groupCallState == GroupCallState.FULL) {
|
||||
|
@ -130,7 +156,7 @@ public final class WebRtcControls {
|
|||
}
|
||||
|
||||
boolean isFadeOutEnabled() {
|
||||
return isAtLeastOutgoing() && isRemoteVideoEnabled;
|
||||
return isAtLeastOutgoing() && isRemoteVideoEnabled && foldableState.isFlat();
|
||||
}
|
||||
|
||||
boolean displaySmallOngoingCallButtons() {
|
||||
|
@ -199,4 +225,35 @@ public final class WebRtcControls {
|
|||
return compareTo(other) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FoldableState {
|
||||
|
||||
private static final int NOT_SET = -1;
|
||||
|
||||
private final int foldPoint;
|
||||
|
||||
public FoldableState(int foldPoint) {
|
||||
this.foldPoint = foldPoint;
|
||||
}
|
||||
|
||||
public boolean isFolded() {
|
||||
return foldPoint != NOT_SET;
|
||||
}
|
||||
|
||||
public boolean isFlat() {
|
||||
return foldPoint == NOT_SET;
|
||||
}
|
||||
|
||||
public int getFoldPoint() {
|
||||
return foldPoint;
|
||||
}
|
||||
|
||||
public static @NonNull FoldableState folded(int foldPoint) {
|
||||
return new FoldableState(foldPoint);
|
||||
}
|
||||
|
||||
public static @NonNull FoldableState flat() {
|
||||
return new FoldableState(NOT_SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package org.thoughtcrime.securesms.messagerequests;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
@ -44,11 +46,17 @@ public class CalleeMustAcceptMessageRequestActivity extends BaseActivity {
|
|||
return intent;
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.callee_must_accept_message_request_dialog_fragment);
|
||||
|
||||
boolean callingFixedToPortrait = getResources().getConfiguration().densityDpi < 480;
|
||||
if (callingFixedToPortrait) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
description = findViewById(R.id.description);
|
||||
avatar = findViewById(R.id.avatar);
|
||||
okay = findViewById(R.id.okay);
|
||||
|
|
|
@ -168,8 +168,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
process((s, p) -> p.handleUpdateRenderedResolutions(s));
|
||||
}
|
||||
|
||||
public void orientationChanged(int degrees) {
|
||||
process((s, p) -> p.handleOrientationChanged(s, degrees));
|
||||
public void orientationChanged(boolean isLandscapeEnabled, int degrees) {
|
||||
process((s, p) -> p.handleOrientationChanged(s, isLandscapeEnabled, degrees));
|
||||
}
|
||||
|
||||
public void setAudioSpeaker(boolean isSpeaker) {
|
||||
|
@ -649,7 +649,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
|
||||
@Override
|
||||
public void onFullyInitialized() {
|
||||
process((s, p) -> p.handleOrientationChanged(s, s.getLocalDeviceState().getOrientation().getDegrees()));
|
||||
process((s, p) -> p.handleOrientationChanged(s, false, s.getLocalDeviceState().getOrientation().getDegrees()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.signal.ringrtc.CallException;
|
|||
import org.signal.ringrtc.CallId;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
|
@ -454,24 +455,27 @@ public abstract class WebRtcActionProcessor {
|
|||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleOrientationChanged(@NonNull WebRtcServiceState currentState, int orientationDegrees) {
|
||||
protected @NonNull WebRtcServiceState handleOrientationChanged(@NonNull WebRtcServiceState currentState, boolean isLandscapeEnabled, int orientationDegrees) {
|
||||
Camera camera = currentState.getVideoState().getCamera();
|
||||
if (camera != null) {
|
||||
camera.setOrientation(orientationDegrees);
|
||||
}
|
||||
|
||||
int sinkRotationDegrees = isLandscapeEnabled ? BroadcastVideoSink.DEVICE_ROTATION_IGNORE : orientationDegrees;
|
||||
int stateRotationDegrees = isLandscapeEnabled ? 0 : orientationDegrees;
|
||||
|
||||
BroadcastVideoSink sink = currentState.getVideoState().getLocalSink();
|
||||
if (sink != null) {
|
||||
sink.setDeviceOrientationDegrees(orientationDegrees);
|
||||
sink.setDeviceOrientationDegrees(sinkRotationDegrees);
|
||||
}
|
||||
|
||||
for (CallParticipant callParticipant : currentState.getCallInfoState().getRemoteCallParticipants()) {
|
||||
callParticipant.getVideoSink().setDeviceOrientationDegrees(orientationDegrees);
|
||||
callParticipant.getVideoSink().setDeviceOrientationDegrees(sinkRotationDegrees);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.setOrientation(Orientation.fromDegrees(orientationDegrees))
|
||||
.setOrientation(Orientation.fromDegrees(stateRotationDegrees))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<gradient android:type="linear"
|
||||
android:angle="90"
|
||||
android:startColor="@color/transparent_black_60" />
|
||||
|
||||
</shape>
|
|
@ -4,10 +4,21 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="org.thoughtcrime.securesms.components.webrtc.WebRtcCallView">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/fold_top_guideline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_end="0dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/call_screen_participants_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/fold_top_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/call_screen_participants_pager"
|
||||
|
@ -21,22 +32,64 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/webrtc_call_view_large_local_render"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<FrameLayout
|
||||
android:id="@+id/call_screen_large_local_renderer_frame"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/black"
|
||||
app:layout_constraintBottom_toTopOf="@id/fold_top_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
|
||||
android:id="@+id/call_screen_large_local_renderer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/call_screen_large_local_video_off_avatar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/call_screen_large_local_video_off"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/WebRtcCallView__your_video_is_off"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body2"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"
|
||||
app:drawableTopCompat="@drawable/ic_video_off_solid_white_28"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/group_call_call_full_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout="@layout/group_call_call_full" />
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout="@layout/group_call_call_full"
|
||||
app:layout_constraintBottom_toTopOf="@id/fold_top_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/call_screen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/fold_top_call_screen_guideline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toTopOf="@id/call_screen_navigation_bar_guideline"
|
||||
app:layout_constraintGuide_end="0dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/call_screen_header_gradient"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -68,19 +121,20 @@
|
|||
|
||||
<View
|
||||
android:id="@+id/call_screen_footer_gradient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="-40dp"
|
||||
android:background="@drawable/webrtc_call_screen_header_gradient"
|
||||
android:rotation="180"
|
||||
android:background="@drawable/webrtc_call_screen_footer_gradient"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/call_screen_footer_gradient_spacer"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/call_screen_participants_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="72dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
|
@ -89,14 +143,19 @@
|
|||
android:visibility="gone"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@id/call_screen_video_toggle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:reverseLayout="true"
|
||||
tools:listitem="@layout/webrtc_call_participant_recycler_item" />
|
||||
tools:itemCount="2"
|
||||
tools:listitem="@layout/webrtc_call_participant_recycler_item"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout
|
||||
android:id="@+id/call_screen_pip_area"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/call_screen_navigation_bar_guideline"
|
||||
app:layout_constraintBottom_toTopOf="@+id/fold_top_call_screen_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
@ -358,6 +417,39 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fold_show_participants_menu_counter_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:alpha="0"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:minWidth="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/fold_top_call_screen_guideline"
|
||||
tools:background="@color/core_ultramarine">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:srcCompat="@drawable/ic_group_solid_24"
|
||||
app:tint="@color/core_white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fold_show_participants_menu_counter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/core_white"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="0" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/call_screen_footer_gradient_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -231,6 +231,9 @@ dependencyVerification {
|
|||
['androidx.viewpager:viewpager:1.0.0',
|
||||
'147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682'],
|
||||
|
||||
['androidx.window:window:1.0.0-alpha09',
|
||||
'edbaafc3a7b0977c138916585d761fe159a521177a30065d676d067edf6571d2'],
|
||||
|
||||
['cn.carbswang.android:NumberPickerView:1.0.9',
|
||||
'18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729'],
|
||||
|
||||
|
@ -507,20 +510,23 @@ dependencyVerification {
|
|||
['org.jetbrains.kotlin:kotlin-reflect:1.4.10',
|
||||
'3ab3413ec945f801448360ad97bc6e14fec6d606889ede3c707cc277b4467f45'],
|
||||
|
||||
['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32',
|
||||
'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'],
|
||||
['org.jetbrains.kotlin:kotlin-stdlib-common:1.5.10',
|
||||
'd958ce94beda85f865829fb95012804866db7d5246615fd71a2f5aabca3e7994'],
|
||||
|
||||
['org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32',
|
||||
'5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6'],
|
||||
['org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0',
|
||||
'ac12f092f12b575c1f9e0ab5025b1e610b0fe95663e26371c16c328895711bae'],
|
||||
|
||||
['org.jetbrains.kotlin:kotlin-stdlib:1.4.32',
|
||||
'13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba'],
|
||||
['org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0',
|
||||
'15e6c81b9e845eefe58d51a04670bb90418046f458264ec0e61ee9bdbc1bfae7'],
|
||||
|
||||
['org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1',
|
||||
'd4cadb673b2101f1ee5fbc147956ac78b1cfd9cc255fb53d3aeb88dff11d99ca'],
|
||||
['org.jetbrains.kotlin:kotlin-stdlib:1.5.10',
|
||||
'ca87c454cd3f2e60931f1803c59699d510d3b4b959cd7119296fb947581d722d'],
|
||||
|
||||
['org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.1',
|
||||
'6d2f87764b6638f27aff12ed380db4b63c9d46ba55dc32683a650598fa5a3e22'],
|
||||
['org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0',
|
||||
'7099198391d673c199fea084423d9f3fdc79470acba19111330c7f88504279c7'],
|
||||
|
||||
['org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0',
|
||||
'78d6cc7135f84d692ff3752fcfd1fa1bbe0940d7df70652e4f1eaeec0c78afbb'],
|
||||
|
||||
['org.jetbrains:annotations:13.0',
|
||||
'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'],
|
||||
|
|
Ładowanie…
Reference in New Issue