mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-06 14:52:28 +00:00
Compare commits
34 Commits
6252
...
android-22
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b6a1e4451 | ||
|
|
4d6ca4383f | ||
|
|
ddce2e6bec | ||
|
|
7dca91a50a | ||
|
|
31348179d4 | ||
|
|
e77679d025 | ||
|
|
44a9363f5b | ||
|
|
bb76090bce | ||
|
|
d0790736db | ||
|
|
0308ba71b1 | ||
|
|
f7d1a5ec80 | ||
|
|
d61fe58fcf | ||
|
|
b428ce2dcd | ||
|
|
d1c9720033 | ||
|
|
c29e8bbdd1 | ||
|
|
3fb3be9727 | ||
|
|
517ec29d85 | ||
|
|
6ad279f029 | ||
|
|
0e98f90205 | ||
|
|
2c5b132483 | ||
|
|
4d8f29d4fe | ||
|
|
22be96d838 | ||
|
|
ccc1157df5 | ||
|
|
f613126776 | ||
|
|
38b21e986d | ||
|
|
38abca8a65 | ||
|
|
f3c6b54ffa | ||
|
|
7dd85bb6ad | ||
|
|
624f88e069 | ||
|
|
fbf693b2dc | ||
|
|
dbf7bf4750 | ||
|
|
d31eb3b248 | ||
|
|
9b75fc98c1 | ||
|
|
1ee9f6a7e5 |
@@ -76,7 +76,8 @@ public class BroadcastAction {
|
||||
OPEN_CHAT("org.jitsi.meet.OPEN_CHAT"),
|
||||
CLOSE_CHAT("org.jitsi.meet.CLOSE_CHAT"),
|
||||
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
|
||||
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED");
|
||||
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED"),
|
||||
SET_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -48,4 +48,10 @@ public class BroadcastIntentHelper {
|
||||
intent.putExtra("muted", muted);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetClosedCaptionsEnabledIntent(boolean enabled) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
|
||||
intent.putExtra("enabled", enabled);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
constants.put("CLOSE_CHAT", BroadcastAction.Type.CLOSE_CHAT.getAction());
|
||||
constants.put("SEND_CHAT_MESSAGE", BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
constants.put("SET_CLOSED_CAPTIONS_ENABLED", BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
protected void onConferenceJoined(HashMap<String, Object> extraData) {
|
||||
JitsiMeetLogger.i("Conference joined: " + extraData);
|
||||
// Launch the service for the ongoing notification.
|
||||
JitsiMeetOngoingConferenceService.launch(this);
|
||||
JitsiMeetOngoingConferenceService.launch(this, extraData);
|
||||
}
|
||||
|
||||
protected void onConferenceTerminated(HashMap<String, Object> extraData) {
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class implements an Android {@link Service}, a foreground one specifically, and it's
|
||||
* responsible for presenting an ongoing notification when a conference is in progress.
|
||||
@@ -39,16 +43,22 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
public class JitsiMeetOngoingConferenceService extends Service
|
||||
implements OngoingConferenceTracker.OngoingConferenceListener {
|
||||
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
|
||||
private static final String EXTRA_DATA_KEY = "extraDataKey";
|
||||
private static final String EXTRA_DATA_BUNDLE_KEY = "extraDataBundleKey";
|
||||
private static final String IS_AUDIO_MUTED_KEY = "isAudioMuted";
|
||||
|
||||
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
|
||||
|
||||
private boolean isAudioMuted;
|
||||
|
||||
static void launch(Context context) {
|
||||
static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
OngoingNotification.createOngoingConferenceNotificationChannel();
|
||||
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
intent.setAction(Action.START.getName());
|
||||
|
||||
Bundle extraDataBundle = new Bundle();
|
||||
extraDataBundle.putSerializable(EXTRA_DATA_KEY, extraData);
|
||||
intent.putExtra(EXTRA_DATA_BUNDLE_KEY, extraDataBundle);
|
||||
|
||||
ComponentName componentName;
|
||||
|
||||
@@ -79,6 +89,15 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
|
||||
OngoingConferenceTracker.getInstance().addListener(this);
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
@@ -101,37 +120,45 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
|
||||
|
||||
if (isAudioMuted != null) {
|
||||
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
|
||||
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
final String actionName = intent.getAction();
|
||||
final Action action = Action.fromName(actionName);
|
||||
|
||||
switch (action) {
|
||||
case UNMUTE:
|
||||
case MUTE:
|
||||
Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
|
||||
break;
|
||||
case START:
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
// When starting the service, there is no action passed in the intent
|
||||
if (action != null) {
|
||||
switch (action) {
|
||||
case UNMUTE:
|
||||
case MUTE:
|
||||
Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
|
||||
break;
|
||||
case HANGUP:
|
||||
JitsiMeetLogger.i(TAG + " Hangup requested");
|
||||
|
||||
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
|
||||
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
break;
|
||||
case HANGUP:
|
||||
JitsiMeetLogger.i(TAG + " Hangup requested");
|
||||
|
||||
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
|
||||
|
||||
stopSelf();
|
||||
break;
|
||||
default:
|
||||
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
|
||||
stopSelf();
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
@@ -147,7 +174,6 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
START(TAG + ":START"),
|
||||
HANGUP(TAG + ":HANGUP"),
|
||||
MUTE(TAG + ":MUTE"),
|
||||
UNMUTE(TAG + ":UNMUTE");
|
||||
@@ -172,6 +198,15 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean tryParseIsAudioMuted(Intent intent) {
|
||||
try {
|
||||
HashMap<String, Object> extraData = (HashMap<String, Object>) intent.getBundleExtra(EXTRA_DATA_BUNDLE_KEY).getSerializable(EXTRA_DATA_KEY);
|
||||
return Boolean.parseBoolean((String) extraData.get(IS_AUDIO_MUTED_KEY));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class BroadcastReceiver extends android.content.BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@@ -180,10 +215,12 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
|
||||
JitsiMeetLogger.i(TAG + " audio muted changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = NAME;
|
||||
|
||||
private static boolean isSupported;
|
||||
private boolean isDisabled;
|
||||
private boolean isEnabled;
|
||||
|
||||
public PictureInPictureModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
@@ -84,7 +84,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public void enterPictureInPicture() {
|
||||
if (isDisabled) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,8 +132,8 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setPictureInPictureDisabled(Boolean disabled) {
|
||||
this.isDisabled = disabled;
|
||||
public void setPictureInPictureEnabled(Boolean enabled) {
|
||||
this.isEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isPictureInPictureSupported() {
|
||||
|
||||
@@ -1759,12 +1759,12 @@ export default {
|
||||
return Promise.reject('Cannot toggle screen sharing: not supported.');
|
||||
}
|
||||
|
||||
if (this.isAudioOnly()) {
|
||||
APP.store.dispatch(setAudioOnly(false));
|
||||
}
|
||||
if (toggle) {
|
||||
try {
|
||||
await this._switchToScreenSharing(options);
|
||||
if (this.isAudioOnly()) {
|
||||
APP.store.dispatch(setAudioOnly(false));
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
@@ -2653,13 +2653,9 @@ export default {
|
||||
// muteVideo logic in such case.
|
||||
const tracks = APP.store.getState()['features/base/tracks'];
|
||||
const isTrackInRedux
|
||||
= Boolean(
|
||||
tracks.find(
|
||||
track => track.jitsiTrack
|
||||
&& track.jitsiTrack.getType() === 'video'));
|
||||
= Boolean(tracks.find(track => track.jitsiTrack && track.jitsiTrack.getType() === MEDIA_TYPE.VIDEO));
|
||||
|
||||
|
||||
if (isTrackInRedux) {
|
||||
if (isTrackInRedux && !this.isSharingScreen) {
|
||||
this.muteVideo(audioOnly);
|
||||
}
|
||||
|
||||
|
||||
27
config.js
27
config.js
@@ -295,8 +295,13 @@ var config = {
|
||||
// Whether to enable live streaming or not.
|
||||
// liveStreamingEnabled: false,
|
||||
|
||||
// Whether to enable local recording or not.
|
||||
// enableLocalRecording: false,
|
||||
// Local recording configuration.
|
||||
// localRecording: {
|
||||
// // Whether to enable local recording or not.
|
||||
// enable: false,
|
||||
// // Whether to notify all participants when a participant is recording locally.
|
||||
// notifyAllParticipants: false
|
||||
// },
|
||||
|
||||
// Transcription (in interface_config,
|
||||
// subtitles and buttons can be configured)
|
||||
@@ -788,14 +793,14 @@ var config = {
|
||||
// // Enables displaying face expressions in speaker stats
|
||||
// enableDisplayFaceExpressions: false,
|
||||
|
||||
// // Enable rtc stats for face landmarks
|
||||
// enableRTCStats: false,
|
||||
|
||||
// // Minimum required face movement percentage threshold for sending new face centering coordinates data.
|
||||
// faceCenteringThreshold: 10,
|
||||
|
||||
// // Milliseconds for processing a new image capture in order to detect face coordinates if they exist.
|
||||
// captureInterval: 1000,
|
||||
|
||||
// // Maximum number of faces that can be detected from a video track.
|
||||
// maxFacesDetected: 4
|
||||
// captureInterval: 1000
|
||||
// },
|
||||
|
||||
// Controls the percentage of automatic feedback shown to participants when callstats is enabled.
|
||||
@@ -875,6 +880,10 @@ var config = {
|
||||
// The Amplitude APP Key:
|
||||
// amplitudeAPPKey: '<APP_KEY>'
|
||||
|
||||
// Obfuscates room name sent to analytics (amplitude, rtcstats)
|
||||
// Default value is false.
|
||||
// obfuscateRoomName: false,
|
||||
|
||||
// Configuration for the rtcstats server:
|
||||
// By enabling rtcstats server every time a conference is joined the rtcstats
|
||||
// module connects to the provided rtcstatsEndpoint and sends statistics regarding
|
||||
@@ -949,12 +958,18 @@ var config = {
|
||||
// chromeExtensionBanner: {
|
||||
// // The chrome extension to be installed address
|
||||
// url: 'https://chrome.google.com/webstore/detail/jitsi-meetings/kglhbbefdnlheedjiejgomgmfplipfeb',
|
||||
// edgeUrl: 'https://microsoftedge.microsoft.com/addons/detail/jitsi-meetings/eeecajlpbgjppibfledfihobcabccihn',
|
||||
|
||||
// // Extensions info which allows checking if they are installed or not
|
||||
// chromeExtensionsInfo: [
|
||||
// {
|
||||
// id: 'kglhbbefdnlheedjiejgomgmfplipfeb',
|
||||
// path: 'jitsi-logo-48x48.png'
|
||||
// },
|
||||
// // Edge extension info
|
||||
// {
|
||||
// id: 'eeecajlpbgjppibfledfihobcabccihn',
|
||||
// path: 'jitsi-logo-48x48.png'
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
|
||||
@@ -8,7 +8,8 @@ import { LoginDialog } from './react/features/authentication/components';
|
||||
import { isTokenAuthEnabled } from './react/features/authentication/functions';
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed
|
||||
connectionFailed,
|
||||
constructOptions
|
||||
} from './react/features/base/connection/actions';
|
||||
import { openDialog } from './react/features/base/dialog/actions';
|
||||
import { setJWT } from './react/features/base/jwt';
|
||||
@@ -19,7 +20,10 @@ import {
|
||||
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
|
||||
import { getCustomerDetails } from './react/features/jaas/actions.any';
|
||||
import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
|
||||
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
|
||||
import {
|
||||
setPrejoinDisplayNameRequired,
|
||||
setPrejoinPageVisibility
|
||||
} from './react/features/prejoin/actions';
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
@@ -81,12 +85,10 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
* Try to open connection using provided credentials.
|
||||
* @param {string} [id]
|
||||
* @param {string} [password]
|
||||
* @param {string} [roomName]
|
||||
* @returns {Promise<JitsiConnection>} connection if
|
||||
* everything is ok, else error.
|
||||
*/
|
||||
export async function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
export async function connect(id, password) {
|
||||
const state = APP.store.getState();
|
||||
let { jwt } = state['features/base/jwt'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
@@ -100,19 +102,7 @@ export async function connect(id, password, roomName) {
|
||||
}
|
||||
}
|
||||
|
||||
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
|
||||
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
|
||||
let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
|
||||
|
||||
serviceUrl += `?room=${roomName}`;
|
||||
|
||||
connectionConfig.serviceUrl = serviceUrl;
|
||||
|
||||
if (connectionConfig.websocketKeepAliveUrl) {
|
||||
connectionConfig.websocketKeepAliveUrl += `?room=${roomName}`;
|
||||
}
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
@@ -257,6 +247,7 @@ function requestAuth(roomName) {
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(setPrejoinPageVisibility(false));
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
|
||||
@@ -28,8 +28,21 @@
|
||||
}
|
||||
|
||||
.local-recording-warning {
|
||||
margin-top: 4px;
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
padding: 8px 16px;
|
||||
|
||||
&.text {
|
||||
color: #fff;
|
||||
background-color: #3D3D3D;
|
||||
}
|
||||
|
||||
&.notification {
|
||||
color: #040404;
|
||||
background-color: #F8AE1A;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-switch-disabled {
|
||||
@@ -46,7 +59,7 @@
|
||||
border-radius: 4px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
width: 56px;
|
||||
width: 42px;
|
||||
}
|
||||
|
||||
.cloud-content-recording-icon-container {
|
||||
@@ -58,7 +71,7 @@
|
||||
}
|
||||
|
||||
.jitsi-recording-header {
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.jitsi-content-recording-icon-container-with-switch {
|
||||
|
||||
@@ -749,4 +749,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: bef1335067eaa4e8c558b1248f8ab3948de855bc
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.11.3
|
||||
|
||||
@@ -27,5 +27,6 @@
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
- (void)sendSetVideoMuted:(BOOL)muted;
|
||||
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,6 +27,7 @@ static NSString * const openChatAction = @"org.jitsi.meet.OPEN_CHAT";
|
||||
static NSString * const closeChatAction = @"org.jitsi.meet.CLOSE_CHAT";
|
||||
static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSAGE";
|
||||
static NSString * const setVideoMutedAction = @"org.jitsi.meet.SET_VIDEO_MUTED";
|
||||
static NSString * const setClosedCaptionsEnabledAction = @"org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED";
|
||||
|
||||
@implementation ExternalAPI
|
||||
|
||||
@@ -49,7 +50,8 @@ RCT_EXPORT_MODULE();
|
||||
@"OPEN_CHAT": openChatAction,
|
||||
@"CLOSE_CHAT": closeChatAction,
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
|
||||
@"SET_VIDEO_MUTED" : setVideoMutedAction
|
||||
@"SET_VIDEO_MUTED" : setVideoMutedAction,
|
||||
@"SET_CLOSED_CAPTIONS_ENABLED": setClosedCaptionsEnabledAction
|
||||
};
|
||||
};
|
||||
|
||||
@@ -73,7 +75,8 @@ RCT_EXPORT_MODULE();
|
||||
openChatAction,
|
||||
closeChatAction,
|
||||
sendChatMessageAction,
|
||||
setVideoMutedAction
|
||||
setVideoMutedAction,
|
||||
setClosedCaptionsEnabledAction
|
||||
];
|
||||
}
|
||||
|
||||
@@ -205,5 +208,10 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:setVideoMutedAction body:data];
|
||||
}
|
||||
|
||||
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled {
|
||||
NSDictionary *data = @{ @"enabled": [NSNumber numberWithBool:enabled]};
|
||||
|
||||
[self sendEventWithName:setClosedCaptionsEnabledAction body:data];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -45,5 +45,6 @@
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled;
|
||||
|
||||
@end
|
||||
|
||||
@@ -160,6 +160,11 @@ static void initializeViewsMap() {
|
||||
[externalAPI sendSetVideoMuted:muted];
|
||||
}
|
||||
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetClosedCaptionsEnabled:enabled];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "نزِّل إضافة متصفح كروم",
|
||||
"buttonTextEdge": "قم بتثبيت ملحق Edge",
|
||||
"close": "إغلق",
|
||||
"dontShowAgain": "لا ترني هذه مرة أخرى",
|
||||
"installExtensionText": "نزِّل الإضافة للدمج مع رزنامة غوغل ورزنامة أوفيس 365"
|
||||
@@ -207,6 +208,9 @@
|
||||
"selectADevice": "اختر جهازًا",
|
||||
"testAudio": "اختبر الصوت"
|
||||
},
|
||||
"dialIn": {
|
||||
"screenTitle": "ملخص الطلب"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "{{status}} الآن"
|
||||
},
|
||||
@@ -814,6 +818,7 @@
|
||||
"initiated": "بدأ الاتصال",
|
||||
"joinAudioByPhone": "انضم مع صوت من الجوال",
|
||||
"joinMeeting": "انضم للمُلتقى",
|
||||
"joinMeetingInLowBandwidthMode": "الانضمام في وضع النطاق الترددي المنخفض",
|
||||
"joinWithoutAudio": "انضم دون صوت",
|
||||
"keyboardShortcuts": "تفعيل اختصارات لوحة المفاتيح",
|
||||
"linkCopied": "نُسِخ الرابط",
|
||||
@@ -948,6 +953,7 @@
|
||||
"playSounds": "تشغيل الصوت عند:",
|
||||
"reactions": "ردود فعل المُلتقى",
|
||||
"sameAsSystem": "مثل النظام ({{label}})",
|
||||
"screenTitle": "إعدادات",
|
||||
"selectAudioOutput": "خرج الصوت",
|
||||
"selectCamera": "الكاميرا",
|
||||
"selectMic": "المجهار (المايكروفون)",
|
||||
@@ -973,6 +979,7 @@
|
||||
"disableCrashReportingWarning": "أمتأكد من تعطيل تقارير الأعطال التقنية؟ ستسري الإعدادات الجديدة بعد إعادة تشغيل التطبيق",
|
||||
"disableP2P": "تعطيل وضع واحد شخص-لشخص",
|
||||
"displayName": "عرض الاسم",
|
||||
"displayNamePlaceholderText": "على سبيل المثال: علي الحيدري",
|
||||
"email": "البريد الإلكتروني",
|
||||
"header": "الإعدادات",
|
||||
"profileSection": "الملف الشخصي",
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Instalar extensão do Chrome",
|
||||
"buttonTextEdge": "Instalar extensão do Edge",
|
||||
"close": "Fechar",
|
||||
"dontShowAgain": "Não me mostre isto outra vez",
|
||||
"installExtensionText": "Instalar a extensão para a integração Google Calendar e Office 365"
|
||||
@@ -194,7 +195,7 @@
|
||||
"unsupportedBrowser": "Parece que está a usar um browser que não suportamos."
|
||||
},
|
||||
"defaultLink": "ex.: {{url}}",
|
||||
"defaultNickname": "ex.: João Pedro",
|
||||
"defaultNickname": "ex.: João Dias",
|
||||
"deviceError": {
|
||||
"cameraError": "Falha ao aceder à sua câmara",
|
||||
"cameraPermission": "Erro ao obter permissão para a câmara",
|
||||
@@ -207,6 +208,9 @@
|
||||
"selectADevice": "Selecione um dispositivo",
|
||||
"testAudio": "Tocar um som de teste"
|
||||
},
|
||||
"dialIn": {
|
||||
"screenTitle": "Resumo da marcação"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "está agora {{status}}"
|
||||
},
|
||||
@@ -288,7 +292,7 @@
|
||||
"lockRoom": "Adicionar reunião $t(lockRoomPassword)",
|
||||
"lockTitle": "Bloqueio falhado",
|
||||
"login": "Entrar",
|
||||
"logoutQuestion": "Tem a certeza de que quer terminar a sessão e interromper a conferência?",
|
||||
"logoutQuestion": "Tem a certeza de que quer terminar a sessão e sair da conferência?",
|
||||
"logoutTitle": "Sair",
|
||||
"maxUsersLimitReached": "O limite para o número máximo de participantes foi atingido. A conferência está cheia. Por favor contacte o proprietário da reunião ou tente novamente mais tarde!",
|
||||
"maxUsersLimitReachedTitle": "Limite máximo de participantes atingido",
|
||||
@@ -657,6 +661,8 @@
|
||||
"linkToSalesforceKey": "Ligar esta reunião",
|
||||
"linkToSalesforceProgress": "A ligar a reunião à Salesforce...",
|
||||
"linkToSalesforceSuccess": "A reunião foi ligada à Salesforce",
|
||||
"localRecordingStarted": "{{name}} iniciou uma gravação local.",
|
||||
"localRecordingStopped": "{{name}} parou uma gravação local.",
|
||||
"me": "Eu",
|
||||
"moderationInEffectCSDescription": "Por favor, levantem a mão se quiserem partilhar o vosso ecrã.",
|
||||
"moderationInEffectCSTitle": "A partilha de ecrã é bloqueada pelo moderador",
|
||||
@@ -812,6 +818,7 @@
|
||||
"initiated": "Chamada iniciada",
|
||||
"joinAudioByPhone": "Entrar com o áudio do telefone",
|
||||
"joinMeeting": "Entrar na reunião",
|
||||
"joinMeetingInLowBandwidthMode": "Entrar em modo de baixa largura de banda",
|
||||
"joinWithoutAudio": "Entrar sem áudio",
|
||||
"keyboardShortcuts": "Ativar os atalhos de teclado",
|
||||
"linkCopied": "Link copiado para a área de transferência",
|
||||
@@ -887,6 +894,7 @@
|
||||
"limitNotificationDescriptionWeb": "Devido à grande procura, a sua gravação será limitada a {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"linkGenerated": "Gerámos um link para a sua gravação.",
|
||||
"live": "DIRETO",
|
||||
"localRecordingWarning": "Certifique-se de seleccionar o separador actual a fim de utilizar o vídeo e áudio correctos. A gravação está actualmente limitada a 1 GB, o que é cerca de 100 minutos.",
|
||||
"loggedIn": "Conectado como {{userName}}",
|
||||
"off": "Gravação parada",
|
||||
"offBy": "{{name}} parou a gravação",
|
||||
@@ -894,6 +902,7 @@
|
||||
"onBy": "{{name}} iniciou a gravação",
|
||||
"pending": "Preparando para gravar a reunião...",
|
||||
"rec": "REC",
|
||||
"saveLocalRecording": "Guardar ficheiro de gravação localmente",
|
||||
"serviceDescription": "Sua gravação será salva pelo serviço de gravação",
|
||||
"serviceDescriptionCloud": "Gravação na nuvem",
|
||||
"serviceDescriptionCloudInfo": "As reuniões gravadas são automaticamente apagadas 24h após a hora de gravação.",
|
||||
@@ -901,11 +910,12 @@
|
||||
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
|
||||
"signIn": "Entrar",
|
||||
"signOut": "Sair",
|
||||
"surfaceError": "Por favor, seleccione o separador actual.",
|
||||
"unavailable": "Oops! O {{serviceName}} está indisponível. Estamos trabalhando para resolver o problema. Por favor, tente mais tarde.",
|
||||
"unavailableTitle": "Gravação indisponível",
|
||||
"uploadToCloud": "Enviar para a nuvem"
|
||||
},
|
||||
"screenshareDisplayName": "Ecrã do {{name}}",
|
||||
"screenshareDisplayName": "Ecrã de {{name}}",
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Puxe para atualizar"
|
||||
},
|
||||
@@ -943,6 +953,7 @@
|
||||
"playSounds": "Reproduzir som quando",
|
||||
"reactions": "Expressarem uma reação",
|
||||
"sameAsSystem": "O mesmo que o sistema ({{label}})",
|
||||
"screenTitle": "Definições",
|
||||
"selectAudioOutput": "Saída de áudio",
|
||||
"selectCamera": "Câmara",
|
||||
"selectMic": "Microfone",
|
||||
@@ -968,6 +979,7 @@
|
||||
"disableCrashReportingWarning": "Tem a certeza de que quer desativar o relatório de falhas? A configuração será aplicada depois de reiniciar a aplicação.",
|
||||
"disableP2P": "Desactivar o modo Peer-To-Peer",
|
||||
"displayName": "Nome de exibição",
|
||||
"displayNamePlaceholderText": "Ex: João Dias",
|
||||
"email": "Email",
|
||||
"header": "Configurações",
|
||||
"profileSection": "Perfil",
|
||||
@@ -1077,7 +1089,7 @@
|
||||
"tileView": "Mudar a vista em quadrícula",
|
||||
"toggleCamera": "Mudar a câmara",
|
||||
"toggleFilmstrip": "Mudar a película de filme",
|
||||
"undock": "Desencaixar numa janela separada",
|
||||
"undock": "Soltar numa janela separada",
|
||||
"videoblur": "Mudar o desfoque de vídeo",
|
||||
"videomute": "Iniciar / Parar câmara"
|
||||
},
|
||||
@@ -1163,7 +1175,7 @@
|
||||
"talkWhileMutedPopup": "Está a tentar falar? Está com o microfone desativado.",
|
||||
"tileViewToggle": "Mudar para vista em quadrícula",
|
||||
"toggleCamera": "Mudar a câmara",
|
||||
"undock": "Desencaixar numa janela separada",
|
||||
"undock": "Soltar numa janela separada",
|
||||
"videoSettings": "Definições de vídeo",
|
||||
"videomute": "Iniciar / Parar câmara"
|
||||
},
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Install Chrome Extension",
|
||||
"buttonTextEdge": "Install Edge Extension",
|
||||
"close": "Close",
|
||||
"dontShowAgain": "Don’t show me this again",
|
||||
"installExtensionText": "Install the extension for Google Calendar and Office 365 integration"
|
||||
@@ -207,6 +208,9 @@
|
||||
"selectADevice": "Select a device",
|
||||
"testAudio": "Play a test sound"
|
||||
},
|
||||
"dialIn": {
|
||||
"screenTitle": "Dial-in summary"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "is now {{status}}"
|
||||
},
|
||||
@@ -814,6 +818,7 @@
|
||||
"initiated": "Call initiated",
|
||||
"joinAudioByPhone": "Join with phone audio",
|
||||
"joinMeeting": "Join meeting",
|
||||
"joinMeetingInLowBandwidthMode": "Join in low bandwidth mode",
|
||||
"joinWithoutAudio": "Join without audio",
|
||||
"keyboardShortcuts": "Enable Keyboard shortcuts",
|
||||
"linkCopied": "Link copied to clipboard",
|
||||
@@ -889,6 +894,7 @@
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"linkGenerated": "We have generated a link to your recording.",
|
||||
"live": "LIVE",
|
||||
"localRecordingNoNotificationWarning": "The recording will not be announced to other participants. You will need to let them know that the meeting is recorded.",
|
||||
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio. The recording is currently limited to 1GB, which is around 100 minutes.",
|
||||
"loggedIn": "Logged in as {{userName}}",
|
||||
"off": "Recording stopped",
|
||||
@@ -948,6 +954,7 @@
|
||||
"playSounds": "Play sound on",
|
||||
"reactions": "Meeting reactions",
|
||||
"sameAsSystem": "Same as system ({{label}})",
|
||||
"screenTitle": "Settings",
|
||||
"selectAudioOutput": "Audio output",
|
||||
"selectCamera": "Camera",
|
||||
"selectMic": "Microphone",
|
||||
|
||||
@@ -1695,6 +1695,19 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that the state of the participants pane changed.
|
||||
*
|
||||
* @param {boolean} open - Wether the panel is open or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyParticipantsPaneToggled(open) {
|
||||
this._sendEvent({
|
||||
name: 'participants-pane-toggled',
|
||||
open
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
8
modules/API/external/external_api.js
vendored
8
modules/API/external/external_api.js
vendored
@@ -126,6 +126,7 @@ const events = {
|
||||
'participant-kicked-out': 'participantKickedOut',
|
||||
'participant-left': 'participantLeft',
|
||||
'participant-role-changed': 'participantRoleChanged',
|
||||
'participants-pane-toggled': 'participantsPaneToggled',
|
||||
'password-required': 'passwordRequired',
|
||||
'proxy-connection-event': 'proxyConnectionEvent',
|
||||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
@@ -301,6 +302,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* the participant opening the meeting.
|
||||
* @param {string} [options.e2eeKey] - The key used for End-to-End encryption.
|
||||
* THIS IS EXPERIMENTAL.
|
||||
* @param {string} [options.release] - The key used for specifying release if enabled on the backend.
|
||||
*/
|
||||
constructor(domain, ...args) {
|
||||
super();
|
||||
@@ -317,7 +319,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
invitees,
|
||||
devices,
|
||||
userInfo,
|
||||
e2eeKey
|
||||
e2eeKey,
|
||||
release
|
||||
} = parseArguments(args);
|
||||
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
|
||||
|
||||
@@ -332,7 +335,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
userInfo,
|
||||
appData: {
|
||||
localStorageContent
|
||||
}
|
||||
},
|
||||
release
|
||||
});
|
||||
this._createIFrame(height, width, onload);
|
||||
this._transport = new Transport({
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { getReplaceParticipant } from '../../../react/features/base/config/functions';
|
||||
import { isDialogOpen } from '../../../react/features/base/dialog';
|
||||
import { setJWT } from '../../../react/features/base/jwt';
|
||||
import { setPrejoinPageVisibility } from '../../../react/features/prejoin';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import ExternalLoginDialog from './LoginDialog';
|
||||
@@ -180,6 +181,7 @@ function authenticate(room: Object, lockPassword: string) {
|
||||
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
|
||||
doExternalAuth(room, lockPassword);
|
||||
} else {
|
||||
APP.store.dispatch(setPrejoinPageVisibility(false));
|
||||
APP.store.dispatch(openLoginDialog());
|
||||
}
|
||||
}
|
||||
|
||||
93
package-lock.json
generated
93
package-lock.json
generated
@@ -72,8 +72,9 @@
|
||||
"jquery": "3.5.1",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1455.0.0+f3a2f61e/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1457.0.0+ad75454f/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.2",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -93,7 +94,7 @@
|
||||
"react-native-collapsible": "1.6.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "9.2.1",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.1.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
@@ -141,7 +142,9 @@
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@babel/runtime": "7.16.0",
|
||||
"@jitsi/eslint-config": "4.0.0",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-native": "0.67.6",
|
||||
"@types/react-redux": "7.1.24",
|
||||
"@types/uuid": "8.3.4",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
@@ -5382,6 +5385,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
|
||||
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-proxy": {
|
||||
"version": "1.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
|
||||
@@ -5478,9 +5491,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "17.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz",
|
||||
"integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==",
|
||||
"version": "17.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
|
||||
"integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@@ -5496,6 +5509,18 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
|
||||
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
|
||||
@@ -11625,6 +11650,11 @@
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.6.1.tgz",
|
||||
"integrity": "sha512-lyUTXOqMEaA9mm38mHxbTo83WsYAvMJm850kxJcRno3T2qL+e40B2G89E0/4r9TdAeB3jN0TdSVp/VHNI6/WyQ=="
|
||||
},
|
||||
"node_modules/js-sha512": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
|
||||
"integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -12138,8 +12168,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1455.0.0+f3a2f61e/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-TNbWjrhOQ2AxUwQyVV5JfWnot+mm4pTcScZ5tHSLPaltz34iiikKjNQJFF5Ji918KtYju/3Xdzti2mt0tFK17A==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1457.0.0+ad75454f/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-K+dJWt6nlAXtKE/WhR8pkf3vga+52tJSpTWX/fxOTKn8IJKTlj46gSC2CosAfwyG4P6ISzeFnTvVC3E+qbxbUg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -15392,9 +15422,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-dialog": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-9.2.1.tgz",
|
||||
"integrity": "sha512-UNnGFTpH0cX16cJZLFq9/TAZH1+B2vzJrQL1mUaSqZjV+sFTpUB1WvghJZxPwi52v587kJpfKN7oPfWaXAu+YQ==",
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.63.0"
|
||||
}
|
||||
@@ -24015,6 +24046,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
|
||||
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
|
||||
@@ -24111,9 +24152,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "17.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz",
|
||||
"integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==",
|
||||
"version": "17.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
|
||||
"integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@@ -24129,6 +24170,18 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
|
||||
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
|
||||
@@ -28897,6 +28950,11 @@
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.6.1.tgz",
|
||||
"integrity": "sha512-lyUTXOqMEaA9mm38mHxbTo83WsYAvMJm850kxJcRno3T2qL+e40B2G89E0/4r9TdAeB3jN0TdSVp/VHNI6/WyQ=="
|
||||
},
|
||||
"js-sha512": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
|
||||
"integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -29311,8 +29369,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1455.0.0+f3a2f61e/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-TNbWjrhOQ2AxUwQyVV5JfWnot+mm4pTcScZ5tHSLPaltz34iiikKjNQJFF5Ji918KtYju/3Xdzti2mt0tFK17A==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1457.0.0+ad75454f/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-K+dJWt6nlAXtKE/WhR8pkf3vga+52tJSpTWX/fxOTKn8IJKTlj46gSC2CosAfwyG4P6ISzeFnTvVC3E+qbxbUg==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -31883,9 +31941,8 @@
|
||||
"integrity": "sha512-92676ZWHZHsPM/EW1ulgb2MuVfjYfMWRTWMbLcrCsipkcMaZ9Traz5mpsnCS7KZpsOksnvUinzDIjsct2XGc6Q=="
|
||||
},
|
||||
"react-native-dialog": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-9.2.1.tgz",
|
||||
"integrity": "sha512-UNnGFTpH0cX16cJZLFq9/TAZH1+B2vzJrQL1mUaSqZjV+sFTpUB1WvghJZxPwi52v587kJpfKN7oPfWaXAu+YQ=="
|
||||
"version": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A=="
|
||||
},
|
||||
"react-native-gesture-handler": {
|
||||
"version": "2.1.0",
|
||||
|
||||
11
package.json
11
package.json
@@ -77,8 +77,9 @@
|
||||
"jquery": "3.5.1",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1455.0.0+f3a2f61e/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1457.0.0+ad75454f/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.2",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -98,7 +99,7 @@
|
||||
"react-native-collapsible": "1.6.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "9.2.1",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.1.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
@@ -146,7 +147,9 @@
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@babel/runtime": "7.16.0",
|
||||
"@jitsi/eslint-config": "4.0.0",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-native": "0.67.6",
|
||||
"@types/react-redux": "7.1.24",
|
||||
"@types/uuid": "8.3.4",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
@@ -189,5 +192,9 @@
|
||||
"postinstall": "patch-package --error-on-fail && jetify",
|
||||
"validate": "npm ls",
|
||||
"start": "make dev"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-dom": "17.0.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
diff --git a/node_modules/react-native-dialog/lib/Button.js b/node_modules/react-native-dialog/lib/Button.js
|
||||
index 19eeb22..b8a66f4 100644
|
||||
--- a/node_modules/react-native-dialog/lib/Button.js
|
||||
+++ b/node_modules/react-native-dialog/lib/Button.js
|
||||
@@ -50,7 +50,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "link_text_dark" : "link_text_dark_light"}`),
|
||||
+ color: isDark ? '#BFC7C7C7' : '#BF727272',
|
||||
textAlign: "center",
|
||||
backgroundColor: "transparent",
|
||||
padding: 8,
|
||||
diff --git a/node_modules/react-native-dialog/lib/CodeInput.js b/node_modules/react-native-dialog/lib/CodeInput.js
|
||||
index eceae56..cc4339d 100644
|
||||
--- a/node_modules/react-native-dialog/lib/CodeInput.js
|
||||
+++ b/node_modules/react-native-dialog/lib/CodeInput.js
|
||||
@@ -97,7 +97,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
color: PlatformColor("label"),
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
|
||||
+ color: isDark ? '#FAFAFA' : '#212121',
|
||||
fontSize: 20,
|
||||
},
|
||||
default: {},
|
||||
@@ -107,7 +107,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
color: PlatformColor("label"),
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
|
||||
+ color: isDark ? '#FAFAFA' : '#212121',
|
||||
fontSize: 14,
|
||||
},
|
||||
default: {},
|
||||
diff --git a/node_modules/react-native-dialog/lib/Container.js b/node_modules/react-native-dialog/lib/Container.js
|
||||
index 69e3764..d7569fb 100644
|
||||
--- a/node_modules/react-native-dialog/lib/Container.js
|
||||
+++ b/node_modules/react-native-dialog/lib/Container.js
|
||||
@@ -82,7 +82,7 @@ DialogContainer.propTypes = {
|
||||
useNativeDriver: PropTypes.bool,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
-const buildStyles = () => StyleSheet.create({
|
||||
+const buildStyles = (isDark) => StyleSheet.create({
|
||||
centeredView: {
|
||||
marginTop: 22,
|
||||
},
|
||||
@@ -103,7 +103,7 @@ const buildStyles = () => StyleSheet.create({
|
||||
overflow: "hidden",
|
||||
},
|
||||
android: {
|
||||
- backgroundColor: PlatformColor("?attr/colorBackgroundFloating"),
|
||||
+ backgroundColor: isDark ? '#212121' : '#FFFFFF',
|
||||
flexDirection: "column",
|
||||
borderRadius: 3,
|
||||
padding: 16,
|
||||
diff --git a/node_modules/react-native-dialog/lib/Description.js b/node_modules/react-native-dialog/lib/Description.js
|
||||
index 2da9ed3..248ac2f 100644
|
||||
--- a/node_modules/react-native-dialog/lib/Description.js
|
||||
+++ b/node_modules/react-native-dialog/lib/Description.js
|
||||
@@ -28,7 +28,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
marginTop: 4,
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "secondary_text_dark" : "secondary_text_light"}`),
|
||||
+ color: isDark ? '#C7C7C7' : '#727272',
|
||||
fontSize: 16,
|
||||
marginTop: 10,
|
||||
},
|
||||
diff --git a/node_modules/react-native-dialog/lib/Input.js b/node_modules/react-native-dialog/lib/Input.js
|
||||
index b33a1a0..063d7f8 100644
|
||||
--- a/node_modules/react-native-dialog/lib/Input.js
|
||||
+++ b/node_modules/react-native-dialog/lib/Input.js
|
||||
@@ -48,7 +48,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
color: PlatformColor("label"),
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
|
||||
+ color: isDark ? '#FAFAFA' : '#212121',
|
||||
fontSize: 14,
|
||||
},
|
||||
default: {},
|
||||
@@ -58,7 +58,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
color: PlatformColor("label"),
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
|
||||
+ color: isDark ? '#FAFAFA' : '#212121',
|
||||
marginLeft: -4,
|
||||
paddingLeft: 4,
|
||||
},
|
||||
diff --git a/node_modules/react-native-dialog/lib/Switch.js b/node_modules/react-native-dialog/lib/Switch.js
|
||||
index 26a05ca..05114fa 100644
|
||||
--- a/node_modules/react-native-dialog/lib/Switch.js
|
||||
+++ b/node_modules/react-native-dialog/lib/Switch.js
|
||||
@@ -52,7 +52,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
flex: 1,
|
||||
paddingRight: 8,
|
||||
fontSize: 16,
|
||||
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
|
||||
+ color: isDark ? '#FAFAFA' : '#212121',
|
||||
},
|
||||
default: {},
|
||||
}),
|
||||
diff --git a/node_modules/react-native-dialog/lib/Title.js b/node_modules/react-native-dialog/lib/Title.js
|
||||
index 1c6fd87..b5511cc 100644
|
||||
--- a/node_modules/react-native-dialog/lib/Title.js
|
||||
+++ b/node_modules/react-native-dialog/lib/Title.js
|
||||
@@ -28,7 +28,7 @@ const buildStyles = (isDark) => StyleSheet.create({
|
||||
fontWeight: "600",
|
||||
},
|
||||
android: {
|
||||
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
|
||||
+ color: isDark ? '#FAFAFA' : '#212121',
|
||||
fontWeight: "500",
|
||||
fontSize: 18,
|
||||
},
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { API_ID } from '../../../modules/API/constants';
|
||||
import { getName as getAppName } from '../app/functions';
|
||||
import { getAnalyticsRoomName } from '../base/conference';
|
||||
import {
|
||||
checkChromeExtensionsInstalled,
|
||||
isMobileBrowser
|
||||
@@ -155,7 +156,9 @@ export async function createHandlers({ getState }: { getState: Function }) {
|
||||
* @param {Array<Object>} handlers - The analytics handlers.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function initAnalytics({ getState }: { getState: Function }, handlers: Array<Object>) {
|
||||
export function initAnalytics(store: Store, handlers: Array<Object>) {
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
if (!isAnalyticsEnabled(getState) || handlers.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -166,7 +169,6 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
|
||||
deploymentInfo
|
||||
} = config;
|
||||
const { group, server } = state['features/base/jwt'];
|
||||
const roomName = state['features/base/conference'].room;
|
||||
const { locationURL = {} } = state['features/base/connection'];
|
||||
const { tenant } = parseURIString(locationURL.href) || {};
|
||||
const permanentProperties = {};
|
||||
@@ -204,7 +206,7 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
|
||||
}
|
||||
|
||||
analytics.addPermanentProperties(permanentProperties);
|
||||
analytics.setConferenceName(roomName);
|
||||
analytics.setConferenceName(getAnalyticsRoomName(state, dispatch));
|
||||
|
||||
// Set the handlers last, since this triggers emptying of the cache
|
||||
analytics.setAnalyticsHandlers(handlers);
|
||||
|
||||
@@ -15,11 +15,17 @@ import { connect, disconnect, setLocationURL } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
|
||||
import { createDesiredLocalTracks } from '../base/tracks';
|
||||
import {
|
||||
appendURLParam,
|
||||
getBackendSafeRoomName,
|
||||
parseURIString,
|
||||
parseURLParams,
|
||||
toURLString
|
||||
} from '../base/util';
|
||||
import { navigateRoot } from '../mobile/navigation/rootNavigationContainerRef';
|
||||
import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
|
||||
import {
|
||||
goBackToRoot,
|
||||
navigateRoot
|
||||
} from '../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
@@ -86,7 +92,11 @@ export function appNavigate(uri: ?string) {
|
||||
let url = `${baseURL}config.js`;
|
||||
|
||||
// XXX In order to support multiple shards, tell the room to the deployment.
|
||||
room && (url += `?room=${getBackendSafeRoomName(room)}`);
|
||||
room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room)));
|
||||
|
||||
const { release } = parseURLParams(location, true, 'search');
|
||||
|
||||
release && (url = appendURLParam(url, 'release', release));
|
||||
|
||||
let config;
|
||||
|
||||
@@ -128,7 +138,15 @@ export function appNavigate(uri: ?string) {
|
||||
|
||||
if (room) {
|
||||
dispatch(createDesiredLocalTracks());
|
||||
dispatch(connect());
|
||||
|
||||
if (isPrejoinPageEnabled(getState())) {
|
||||
navigateRoot(screen.preJoin);
|
||||
} else {
|
||||
dispatch(connect());
|
||||
navigateRoot(screen.conference.root);
|
||||
}
|
||||
} else {
|
||||
goBackToRoot(getState(), dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ import {
|
||||
import { setLocationURL } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet/functions.web';
|
||||
import {
|
||||
appendURLParam,
|
||||
getBackendSafeRoomName,
|
||||
parseURIString
|
||||
parseURIString,
|
||||
parseURLParams
|
||||
} from '../base/util';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import {
|
||||
@@ -93,7 +95,11 @@ export function appNavigate(uri: ?string) {
|
||||
let url = `${baseURL}config.js`;
|
||||
|
||||
// XXX In order to support multiple shards, tell the room to the deployment.
|
||||
room && (url += `?room=${getBackendSafeRoomName(room)}`);
|
||||
room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room)));
|
||||
|
||||
const { release } = parseURLParams(location, true, 'search');
|
||||
|
||||
release && (url = appendURLParam(url, 'release', release));
|
||||
|
||||
let config;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { FullWindowOverlay } from 'react-native-screens';
|
||||
import SplashScreen from 'react-native-splash-screen';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import BottomSheetContainer from '../../base/dialog/components/native/BottomSheetContainer';
|
||||
import { updateFlags } from '../../base/flags/actions';
|
||||
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { Platform } from '../../base/react';
|
||||
import { DimensionsDetector, clientResized, setSafeAreaInsets } from '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import { _getRouteToRender } from '../getRouteToRender.native';
|
||||
@@ -23,6 +23,11 @@ import '../reducers';
|
||||
|
||||
declare var __DEV__;
|
||||
|
||||
const DialogContainerWrapper = Platform.select({
|
||||
ios: FullWindowOverlay,
|
||||
default: View
|
||||
});
|
||||
|
||||
/**
|
||||
* The type of React {@code Component} props of {@link App}.
|
||||
*/
|
||||
@@ -240,7 +245,12 @@ export class App extends AbstractApp {
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return (
|
||||
<DialogContainer />
|
||||
<DialogContainerWrapper
|
||||
pointerEvents = 'box-none'
|
||||
style = { StyleSheet.absoluteFill }>
|
||||
<BottomSheetContainer />
|
||||
<DialogContainer />
|
||||
</DialogContainerWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { toState } from '../base/redux';
|
||||
import { Conference } from '../conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking';
|
||||
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
||||
import { BlankPage, isWelcomePageUserEnabled, WelcomePage } from '../welcome';
|
||||
import { BlankPage, WelcomePage } from '../welcome';
|
||||
import { isWelcomePageEnabled } from '../welcome/functions';
|
||||
|
||||
/**
|
||||
* Determines which route is to be rendered in order to depict a specific Redux
|
||||
@@ -72,7 +73,7 @@ function _getWebConferenceRoute(state) {
|
||||
function _getWebWelcomePageRoute(state) {
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
if (isWelcomePageUserEnabled(state)) {
|
||||
if (isWelcomePageEnabled(state)) {
|
||||
if (isSupportedBrowser()) {
|
||||
route.component = WelcomePage;
|
||||
} else {
|
||||
|
||||
@@ -36,6 +36,7 @@ import '../large-video/middleware';
|
||||
import '../lobby/middleware';
|
||||
import '../notifications/middleware';
|
||||
import '../overlay/middleware';
|
||||
import '../participants-pane/middleware';
|
||||
import '../polls/middleware';
|
||||
import '../reactions/middleware';
|
||||
import '../recent-list/middleware';
|
||||
|
||||
@@ -29,14 +29,11 @@ export function authenticateAndUpgradeRole(
|
||||
id: string,
|
||||
password: string,
|
||||
conference: Object) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const { password: roomPassword }
|
||||
= getState()['features/base/conference'];
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
const process
|
||||
= conference.authenticateAndUpgradeRole({
|
||||
id,
|
||||
password,
|
||||
roomPassword,
|
||||
|
||||
onLoginSuccessful() {
|
||||
// When the login succeeds, the process has completed half
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import Dialog from 'react-native-dialog';
|
||||
import { connect as reduxConnect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { toJid } from '../../../base/connection';
|
||||
import { connect } from '../../../base/connection/actions.native';
|
||||
import { _abstractMapStateToProps } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import type { StyleType } from '../../../base/styles';
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../../actions.native';
|
||||
|
||||
// Register styles.
|
||||
import './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@link LoginDialog}.
|
||||
*/
|
||||
@@ -44,22 +36,12 @@ type Props = {
|
||||
*/
|
||||
_error: Object,
|
||||
|
||||
/**
|
||||
* Extra handler for cancel functionality.
|
||||
*/
|
||||
_onCancel: Function,
|
||||
|
||||
/**
|
||||
* The progress in the floating range between 0 and 1 of the authenticating
|
||||
* and upgrading the role of the local participant/user.
|
||||
*/
|
||||
_progress: number,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of this feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
@@ -68,12 +50,7 @@ type Props = {
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* Override the default visibility.
|
||||
*/
|
||||
visible: boolean
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -120,10 +97,6 @@ type State = {
|
||||
* of the configuration parameters.
|
||||
*/
|
||||
class LoginDialog extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new LoginDialog instance.
|
||||
*
|
||||
@@ -154,42 +127,40 @@ class LoginDialog extends Component<Props, State> {
|
||||
render() {
|
||||
const {
|
||||
_connecting: connecting,
|
||||
t,
|
||||
visible
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Dialog.Container
|
||||
visible = { visible }>
|
||||
<Dialog.Title>
|
||||
{ t('dialog.login') }
|
||||
</Dialog.Title>
|
||||
<Dialog.Input
|
||||
autoCapitalize = { 'none' }
|
||||
autoCorrect = { false }
|
||||
onChangeText = { this._onUsernameChange }
|
||||
placeholder = { 'user@domain.com' }
|
||||
spellCheck = { false }
|
||||
value = { this.state.username } />
|
||||
<Dialog.Input
|
||||
autoCapitalize = { 'none' }
|
||||
onChangeText = { this._onPasswordChange }
|
||||
placeholder = { t('dialog.userPassword') }
|
||||
secureTextEntry = { true }
|
||||
value = { this.state.password } />
|
||||
<Dialog.Description>
|
||||
{ this._renderMessage() }
|
||||
</Dialog.Description>
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Cancel') }
|
||||
onPress = { this._onCancel } />
|
||||
<Dialog.Button
|
||||
disabled = { connecting }
|
||||
label = { t('dialog.Ok') }
|
||||
onPress = { this._onLogin } />
|
||||
</Dialog.Container>
|
||||
</View>
|
||||
<Dialog.Container
|
||||
coverScreen = { false }
|
||||
visible = { true }>
|
||||
<Dialog.Title>
|
||||
{ t('dialog.login') }
|
||||
</Dialog.Title>
|
||||
<Dialog.Input
|
||||
autoCapitalize = { 'none' }
|
||||
autoCorrect = { false }
|
||||
onChangeText = { this._onUsernameChange }
|
||||
placeholder = { 'user@domain.com' }
|
||||
spellCheck = { false }
|
||||
value = { this.state.username } />
|
||||
<Dialog.Input
|
||||
autoCapitalize = { 'none' }
|
||||
onChangeText = { this._onPasswordChange }
|
||||
placeholder = { t('dialog.userPassword') }
|
||||
secureTextEntry = { true }
|
||||
value = { this.state.password } />
|
||||
<Dialog.Description>
|
||||
{ this._renderMessage() }
|
||||
</Dialog.Description>
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Cancel') }
|
||||
onPress = { this._onCancel } />
|
||||
<Dialog.Button
|
||||
disabled = { connecting }
|
||||
label = { t('dialog.Ok') }
|
||||
onPress = { this._onLogin } />
|
||||
</Dialog.Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,12 +175,10 @@ class LoginDialog extends Component<Props, State> {
|
||||
_connecting: connecting,
|
||||
_error: error,
|
||||
_progress: progress,
|
||||
_styles: styles,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
let messageKey;
|
||||
let messageIsError = false;
|
||||
const messageOptions = {};
|
||||
|
||||
if (progress && progress < 1) {
|
||||
@@ -230,34 +199,22 @@ class LoginDialog extends Component<Props, State> {
|
||||
this.props._configHosts)
|
||||
&& credentials.password === this.state.password) {
|
||||
messageKey = 'dialog.incorrectPassword';
|
||||
messageIsError = true;
|
||||
}
|
||||
} else if (name) {
|
||||
messageKey = 'dialog.connectErrorWithMsg';
|
||||
messageOptions.msg = `${name} ${error.message}`;
|
||||
messageIsError = true;
|
||||
}
|
||||
} else if (connecting) {
|
||||
messageKey = 'connection.CONNECTING';
|
||||
}
|
||||
|
||||
if (messageKey) {
|
||||
const message = t(messageKey, messageOptions);
|
||||
const messageStyles
|
||||
= messageIsError ? styles.errorMessage : styles.progressMessage;
|
||||
|
||||
return (
|
||||
<Text style = { messageStyles }>
|
||||
{ message }
|
||||
</Text>
|
||||
);
|
||||
return t(messageKey, messageOptions);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onUsernameChange: (string) => void;
|
||||
|
||||
/**
|
||||
* Called when user edits the username.
|
||||
*
|
||||
@@ -271,8 +228,6 @@ class LoginDialog extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
_onPasswordChange: (string) => void;
|
||||
|
||||
/**
|
||||
* Called when user edits the password.
|
||||
*
|
||||
@@ -286,8 +241,6 @@ class LoginDialog extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
_onCancel: () => void;
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that it has been dismissed by cancel.
|
||||
*
|
||||
@@ -295,14 +248,9 @@ class LoginDialog extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
const { _onCancel, dispatch } = this.props;
|
||||
|
||||
_onCancel && _onCancel();
|
||||
dispatch(cancelLogin());
|
||||
this.props.dispatch(cancelLogin());
|
||||
}
|
||||
|
||||
_onLogin: () => void;
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that the login button (OK) has been pressed by
|
||||
* the user.
|
||||
@@ -355,8 +303,7 @@ function _mapStateToProps(state) {
|
||||
_configHosts: configHosts,
|
||||
_connecting: Boolean(connecting) || Boolean(thenableWithCancel),
|
||||
_error: connectionError || authenticateAndUpgradeRoleError,
|
||||
_progress: progress,
|
||||
_styles: ColorSchemeRegistry.get(state, 'LoginDialog')
|
||||
_progress: progress
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { cancelWaitForOwner } from '../../actions.native';
|
||||
|
||||
import LoginDialog from './LoginDialog';
|
||||
import { openLoginDialog, cancelWaitForOwner } from '../../actions.native';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
@@ -42,14 +38,9 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showLoginDialog: false
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onLogin = this._onLogin.bind(this);
|
||||
this._onLoginDialogCancel = this._onLoginDialogCancel.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,17 +56,10 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
confirmLabel = 'dialog.IamHost'
|
||||
descriptionKey = 'dialog.WaitForHostMsg'
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onLogin }>
|
||||
<LoginDialog
|
||||
// eslint-disable-next-line react/jsx-handler-names
|
||||
_onCancel = { this._onLoginDialogCancel }
|
||||
visible = { this.state.showLoginDialog } />
|
||||
</ConfirmDialog>
|
||||
onSubmit = { this._onLogin } />
|
||||
);
|
||||
}
|
||||
|
||||
_onCancel: () => void;
|
||||
|
||||
/**
|
||||
* Called when the cancel button is clicked.
|
||||
*
|
||||
@@ -86,8 +70,6 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
this.props.dispatch(cancelWaitForOwner());
|
||||
}
|
||||
|
||||
_onLogin: () => void;
|
||||
|
||||
/**
|
||||
* Called when the OK button is clicked.
|
||||
*
|
||||
@@ -95,17 +77,7 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLogin() {
|
||||
this.setState({ showLoginDialog: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the nested login dialog is cancelled.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLoginDialogCancel() {
|
||||
this.setState({ showLoginDialog: false });
|
||||
this.props.dispatch(openLoginDialog());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel } from '../../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the authentication feature.
|
||||
*/
|
||||
ColorSchemeRegistry.register('LoginDialog', {
|
||||
|
||||
/**
|
||||
* The style of {@code Text} rendered by the {@code Dialog}s of the
|
||||
* feature authentication.
|
||||
*/
|
||||
dialogText: {
|
||||
margin: BoxModel.margin,
|
||||
marginTop: BoxModel.margin * 2
|
||||
},
|
||||
|
||||
/**
|
||||
* The style used when an error message is rendered.
|
||||
*/
|
||||
errorMessage: {
|
||||
color: schemeColor('errorText')
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of {@code LoginDialog}.
|
||||
*/
|
||||
loginDialog: {
|
||||
flex: 0,
|
||||
flexDirection: 'column',
|
||||
marginBottom: BoxModel.margin,
|
||||
marginTop: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* The style used then a progress message is rendered.
|
||||
*/
|
||||
progressMessage: {
|
||||
color: schemeColor('text')
|
||||
}
|
||||
});
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
JitsiConnectionErrors
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { setPrejoinPageVisibility } from '../prejoin';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
@@ -120,6 +121,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
&& error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
&& typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
store.dispatch(setPrejoinPageVisibility(false));
|
||||
store.dispatch(openLoginDialog());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -51,9 +51,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
dispatch(hideLoginDialog());
|
||||
|
||||
const { authRequired, conference } = getState()['features/base/conference'];
|
||||
const { passwordRequired } = getState()['features/base/connection'];
|
||||
|
||||
// Only end the meeting if we are not already inside and trying to upgrade.
|
||||
if (authRequired && !conference) {
|
||||
if ((authRequired && !conference) || passwordRequired) {
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ export default {
|
||||
inviteButtonBackground: 'rgb(0, 119, 225)',
|
||||
onVideoText: 'white'
|
||||
},
|
||||
'Dialog': {
|
||||
border: 'rgba(0, 3, 6, 0.6)',
|
||||
buttonBackground: ColorPalette.blue,
|
||||
buttonLabel: ColorPalette.white
|
||||
},
|
||||
'Dialog': {},
|
||||
'Header': {
|
||||
background: ColorPalette.blue,
|
||||
icon: ColorPalette.white,
|
||||
@@ -41,10 +37,6 @@ export default {
|
||||
statusBarContent: ColorPalette.white,
|
||||
text: ColorPalette.white
|
||||
},
|
||||
'Modal': {},
|
||||
'LargeVideo': {
|
||||
background: '#040404'
|
||||
},
|
||||
'Toolbox': {
|
||||
button: 'rgb(255, 255, 255)',
|
||||
buttonToggled: 'rgb(38, 58, 76)',
|
||||
|
||||
@@ -206,6 +206,16 @@ export const SEND_TONES = 'SEND_TONES';
|
||||
*/
|
||||
export const SET_FOLLOW_ME = 'SET_FOLLOW_ME';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the obfuscated room name.
|
||||
*
|
||||
* {
|
||||
* type: SET_OBFUSCATED_ROOM,
|
||||
* obfuscatedRoom: string
|
||||
* }
|
||||
*/
|
||||
export const SET_OBFUSCATED_ROOM = 'SET_OBFUSCATED_ROOM';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which updates the current known status of the
|
||||
* Mute Reactions Sound feature.
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
P2P_STATUS_CHANGED,
|
||||
SEND_TONES,
|
||||
SET_FOLLOW_ME,
|
||||
SET_OBFUSCATED_ROOM,
|
||||
SET_PASSWORD,
|
||||
SET_PASSWORD_FAILED,
|
||||
SET_ROOM,
|
||||
@@ -804,6 +805,24 @@ export function setPassword(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the obfuscated room name of the conference to be joined.
|
||||
*
|
||||
* @param {(string)} obfuscatedRoom - Obfuscated room name.
|
||||
* @param {(string)} obfuscatedRoomSource - The room name that was obfuscated.
|
||||
* @returns {{
|
||||
* type: SET_OBFUSCATED_ROOM,
|
||||
* room: string
|
||||
* }}
|
||||
*/
|
||||
export function setObfuscatedRoom(obfuscatedRoom: string, obfuscatedRoomSource: string) {
|
||||
return {
|
||||
type: SET_OBFUSCATED_ROOM,
|
||||
obfuscatedRoom,
|
||||
obfuscatedRoomSource
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (the name of) the room of the conference to be joined.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { sha512_256 as sha512 } from 'js-sha512';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getName } from '../../app/functions';
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
safeDecodeURIComponent
|
||||
} from '../util';
|
||||
|
||||
import { setObfuscatedRoom } from './actions';
|
||||
import {
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
@@ -298,6 +300,47 @@ export function getRoomName(state: Object): string {
|
||||
return getConferenceState(state).room;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an obfuscated room name or create and persist it if it doesn't exists.
|
||||
*
|
||||
* @param {Object} state - The current state of the app.
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @returns {string} - Obfuscated room name.
|
||||
*/
|
||||
export function getOrCreateObfuscatedRoomName(state: Object, dispatch: Function) {
|
||||
let { obfuscatedRoom } = getConferenceState(state);
|
||||
const { obfuscatedRoomSource } = getConferenceState(state);
|
||||
const room = getRoomName(state);
|
||||
|
||||
// On native mobile the store doesn't clear when joining a new conference so we might have the obfuscatedRoom
|
||||
// stored even though a different room was joined.
|
||||
// Check if the obfuscatedRoom was already computed for the current room.
|
||||
if (!obfuscatedRoom || (obfuscatedRoomSource !== room)) {
|
||||
obfuscatedRoom = sha512(room);
|
||||
dispatch(setObfuscatedRoom(obfuscatedRoom, room));
|
||||
}
|
||||
|
||||
return obfuscatedRoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analytics may require an obfuscated room name, this functions decides based on a config if the normal or
|
||||
* obfuscated room name should be returned.
|
||||
*
|
||||
* @param {Object} state - The current state of the app.
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @returns {string} - Analytics room name.
|
||||
*/
|
||||
export function getAnalyticsRoomName(state: Object, dispatch: Function) {
|
||||
const { analysis: { obfuscateRoomName = false } = {} } = state['features/base/config'];
|
||||
|
||||
if (obfuscateRoomName) {
|
||||
return getOrCreateObfuscatedRoomName(state, dispatch);
|
||||
}
|
||||
|
||||
return getRoomName(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of getWiFiStats from the global NS or does nothing
|
||||
* (returns empty result).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { setPictureInPictureDisabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
@@ -41,7 +41,7 @@ function _toggleScreenSharing(enabled, store) {
|
||||
}
|
||||
} else {
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
setPictureInPictureDisabled(false);
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ function _toggleScreenSharing(enabled, store) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startScreenSharing(dispatch, state) {
|
||||
setPictureInPictureDisabled(true);
|
||||
setPictureInPictureEnabled(false);
|
||||
|
||||
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
|
||||
.then(tracks => {
|
||||
@@ -73,6 +73,6 @@ function _startScreenSharing(dispatch, state) {
|
||||
.catch(error => {
|
||||
console.log('ERROR creating ScreeSharing stream ', error);
|
||||
|
||||
setPictureInPictureDisabled(false);
|
||||
setPictureInPictureEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_OBFUSCATED_ROOM,
|
||||
SET_PASSWORD,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_ROOM,
|
||||
@@ -88,6 +89,12 @@ ReducerRegistry.register(
|
||||
case SET_LOCATION_URL:
|
||||
return set(state, 'room', undefined);
|
||||
|
||||
case SET_OBFUSCATED_ROOM:
|
||||
return { ...state,
|
||||
obfuscatedRoom: action.obfuscatedRoom,
|
||||
obfuscatedRoomSource: action.obfuscatedRoomSource
|
||||
};
|
||||
|
||||
case SET_PASSWORD:
|
||||
return _setPassword(state, action);
|
||||
|
||||
|
||||
@@ -143,7 +143,6 @@ export default [
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'enableLobbyChat',
|
||||
'enableLocalRecording',
|
||||
'enableOpusRed',
|
||||
'enableRemb',
|
||||
'enableSaveLogs',
|
||||
@@ -185,6 +184,7 @@ export default [
|
||||
'ignoreStartMuted',
|
||||
'inviteAppName',
|
||||
'liveStreamingEnabled',
|
||||
'localRecording',
|
||||
'localSubject',
|
||||
'maxFullResolutionParticipants',
|
||||
'mouseMoveCallbackInterval',
|
||||
|
||||
71
react/features/base/connection/actions.any.js
Normal file
71
react/features/base/connection/actions.any.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
appendURLParam,
|
||||
getBackendSafeRoomName,
|
||||
parseURIString
|
||||
} from '../util';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Constructs options to be passed to the constructor of {@code JitsiConnection}
|
||||
* based on the redux state.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object} The options to be passed to the constructor of
|
||||
* {@code JitsiConnection}.
|
||||
*/
|
||||
export function constructOptions(state) {
|
||||
// Deep clone the options to make sure we don't modify the object in the
|
||||
// redux store.
|
||||
const options = _.cloneDeep(state['features/base/config']);
|
||||
|
||||
let { bosh, websocket } = options;
|
||||
|
||||
// TESTING: Only enable WebSocket for some percentage of users.
|
||||
if (websocket && navigator.product === 'ReactNative') {
|
||||
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
|
||||
websocket = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh && !websocket) {
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL.href);
|
||||
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket is preferred over BOSH.
|
||||
const serviceUrl = websocket || bosh;
|
||||
|
||||
logger.log(`Using service URL ${serviceUrl}`);
|
||||
|
||||
// Append room to the URL's search.
|
||||
const { room } = state['features/base/conference'];
|
||||
|
||||
if (serviceUrl && room) {
|
||||
const roomName = getBackendSafeRoomName(room);
|
||||
|
||||
options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName);
|
||||
|
||||
if (options.websocketKeepAliveUrl) {
|
||||
options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -1,15 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
getBackendSafeRoomName,
|
||||
parseURIString
|
||||
} from '../util';
|
||||
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
@@ -18,9 +13,12 @@ import {
|
||||
CONNECTION_WILL_CONNECT,
|
||||
SET_LOCATION_URL
|
||||
} from './actionTypes';
|
||||
import { constructOptions } from './actions.any';
|
||||
import { JITSI_CONNECTION_URL_KEY } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* The error structure passed to the {@link connectionFailed} action.
|
||||
*
|
||||
@@ -78,7 +76,7 @@ export type ConnectionFailedError = {
|
||||
export function connect(id: ?string, password: ?string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const options = _constructOptions(state);
|
||||
const options = constructOptions(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
|
||||
@@ -262,69 +260,6 @@ function _connectionWillConnect(connection) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs options to be passed to the constructor of {@code JitsiConnection}
|
||||
* based on the redux state.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object} The options to be passed to the constructor of
|
||||
* {@code JitsiConnection}.
|
||||
*/
|
||||
function _constructOptions(state) {
|
||||
// Deep clone the options to make sure we don't modify the object in the
|
||||
// redux store.
|
||||
const options = _.cloneDeep(state['features/base/config']);
|
||||
|
||||
let { bosh, websocket } = options;
|
||||
|
||||
// TESTING: Only enable WebSocket for some percentage of users.
|
||||
if (websocket) {
|
||||
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
|
||||
websocket = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh && !websocket) {
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL.href);
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket is preferred over BOSH.
|
||||
const serviceUrl = websocket || bosh;
|
||||
|
||||
logger.log(`Using service URL ${serviceUrl}`);
|
||||
|
||||
// Append room to the URL's search.
|
||||
const { room } = state['features/base/conference'];
|
||||
|
||||
if (serviceUrl && room) {
|
||||
const roomName = getBackendSafeRoomName(room);
|
||||
|
||||
options.serviceUrl = `${serviceUrl}?room=${roomName}`;
|
||||
|
||||
if (options.websocketKeepAliveUrl) {
|
||||
options.websocketKeepAliveUrl += `?room=${roomName}`;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,8 @@ export {
|
||||
} from './actions.native';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
*/
|
||||
export const HIDE_DIALOG = 'HIDE_DIALOG';
|
||||
|
||||
/**
|
||||
* The type of Redux action which closes a sheet.
|
||||
*
|
||||
* {
|
||||
* type: HIDE_SHEET
|
||||
* }
|
||||
*/
|
||||
export const HIDE_SHEET = 'HIDE_SHEET';
|
||||
|
||||
/**
|
||||
* The type of Redux action which begins a request to open a dialog.
|
||||
*
|
||||
@@ -18,3 +27,15 @@ export const HIDE_DIALOG = 'HIDE_DIALOG';
|
||||
*
|
||||
*/
|
||||
export const OPEN_DIALOG = 'OPEN_DIALOG';
|
||||
|
||||
/**
|
||||
* The type of Redux action which begins a request to open a sheet.
|
||||
*
|
||||
* {
|
||||
* type: OPEN_SHEET,
|
||||
* component: React.Component,
|
||||
* props: PropTypes
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export const OPEN_SHEET = 'OPEN_SHEET';
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { HIDE_DIALOG, OPEN_DIALOG } from './actionTypes';
|
||||
import {
|
||||
HIDE_DIALOG,
|
||||
HIDE_SHEET,
|
||||
OPEN_DIALOG,
|
||||
OPEN_SHEET
|
||||
} from './actionTypes';
|
||||
import { isDialogOpen } from './functions';
|
||||
|
||||
/**
|
||||
@@ -24,6 +29,19 @@ export function hideDialog(component: ?Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the active sheet.
|
||||
*
|
||||
* @returns {{
|
||||
* type: HIDE_SHEET,
|
||||
* }}
|
||||
*/
|
||||
export function hideSheet() {
|
||||
return {
|
||||
type: HIDE_SHEET
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals Dialog to open dialog.
|
||||
*
|
||||
@@ -44,6 +62,26 @@ export function openDialog(component: Object, componentProps: ?Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the requested sheet.
|
||||
*
|
||||
* @param {Object} component - The component to display as a sheet.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
* @returns {{
|
||||
* type: OPEN_SHEET,
|
||||
* component: React.Component,
|
||||
* componentProps: (Object | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function openSheet(component: Object, componentProps: ?Object) {
|
||||
return {
|
||||
type: OPEN_SHEET,
|
||||
component,
|
||||
componentProps
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals Dialog to open a dialog with the specified component if the component
|
||||
* is not already open. If it is open, then Dialog is signaled to close its
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Dialog from 'react-native-dialog';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
@@ -46,16 +43,16 @@ class AlertDialog extends AbstractDialog<Props> {
|
||||
: renderHTML(t(contentKey.key, contentKey.params));
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Dialog.Container visible = { true }>
|
||||
<Dialog.Description>
|
||||
{ content }
|
||||
</Dialog.Description>
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Ok') }
|
||||
onPress = { this._onSubmit } />
|
||||
</Dialog.Container>
|
||||
</View>
|
||||
<Dialog.Container
|
||||
coverScreen = { false }
|
||||
visible = { true }>
|
||||
<Dialog.Description>
|
||||
{ content }
|
||||
</Dialog.Description>
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Ok') }
|
||||
onPress = { this._onSubmit } />
|
||||
</Dialog.Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import React, { PureComponent, type Node } from 'react';
|
||||
import { SafeAreaView, ScrollView, View } from 'react-native';
|
||||
|
||||
import { SlidingView } from '../../../react';
|
||||
import { connect } from '../../../redux';
|
||||
import { hideSheet } from '../../actions';
|
||||
|
||||
import { bottomSheetStyles as styles } from './styles';
|
||||
|
||||
@@ -20,6 +22,11 @@ type Props = {
|
||||
*/
|
||||
children: Node,
|
||||
|
||||
/**
|
||||
* Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Handler for the cancel event, which happens when the user dismisses
|
||||
* the sheet.
|
||||
@@ -61,6 +68,31 @@ class BottomSheet extends PureComponent<Props> {
|
||||
showSlidingView: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the cancel event, when the user dismissed the sheet. By default we close it.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
if (this.props.onCancel) {
|
||||
this.props.onCancel();
|
||||
} else {
|
||||
this.props.dispatch(hideSheet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -80,7 +112,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
<SlidingView
|
||||
accessibilityRole = 'menu'
|
||||
accessibilityViewIsModal = { true }
|
||||
onHide = { this.props.onCancel }
|
||||
onHide = { this._onCancel }
|
||||
position = 'bottom'
|
||||
show = { showSlidingView }>
|
||||
<View
|
||||
@@ -116,4 +148,4 @@ class BottomSheet extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default BottomSheet;
|
||||
export default connect()(BottomSheet);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
|
||||
const BottomSheetContainer: () => JSX.Element = () => {
|
||||
const { sheet, sheetProps } = useSelector(state => state['features/base/dialog']);
|
||||
const { reducedUI } = useSelector(state => state['features/base/responsive-ui']);
|
||||
|
||||
if (!sheet || reducedUI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ React.createElement(sheet, sheetProps) }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default BottomSheetContainer;
|
||||
@@ -1,7 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Dialog from 'react-native-dialog';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
@@ -26,7 +23,7 @@ type Props = {
|
||||
/**
|
||||
* The React {@code Component} children.
|
||||
*/
|
||||
children?: React$Node,
|
||||
children?: Node,
|
||||
|
||||
/**
|
||||
* The i18n key of the text label for the confirm button.
|
||||
@@ -111,25 +108,25 @@ class ConfirmDialog extends AbstractDialog<Props> {
|
||||
? styles.destructiveDialogButton : styles.dialogButton;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Dialog.Container visible = { true }>
|
||||
{
|
||||
title && <Dialog.Title>
|
||||
{ t(title) }
|
||||
</Dialog.Title>
|
||||
}
|
||||
{ this._renderDescription() }
|
||||
{ children }
|
||||
<Dialog.Button
|
||||
label = { t(cancelLabel || 'dialog.confirmNo') }
|
||||
onPress = { this._onCancel }
|
||||
style = { styles.dialogButton } />
|
||||
<Dialog.Button
|
||||
label = { t(confirmLabel || 'dialog.confirmYes') }
|
||||
onPress = { this._onSubmit }
|
||||
style = { dialogButtonStyle } />
|
||||
</Dialog.Container>
|
||||
</View>
|
||||
<Dialog.Container
|
||||
coverScreen = { false }
|
||||
visible = { true }>
|
||||
{
|
||||
title && <Dialog.Title>
|
||||
{ t(title) }
|
||||
</Dialog.Title>
|
||||
}
|
||||
{ this._renderDescription() }
|
||||
{ children }
|
||||
<Dialog.Button
|
||||
label = { t(cancelLabel || 'dialog.confirmNo') }
|
||||
onPress = { this._onCancel }
|
||||
style = { styles.dialogButton } />
|
||||
<Dialog.Button
|
||||
label = { t(confirmLabel || 'dialog.confirmYes') }
|
||||
onPress = { this._onSubmit }
|
||||
style = { dialogButtonStyle } />
|
||||
</Dialog.Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { ReactionEmoji } from '../../../../reactions/components';
|
||||
import { getReactionsQueue } from '../../../../reactions/functions.any';
|
||||
@@ -38,10 +38,12 @@ class DialogContainer extends AbstractDialogContainer {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (<React.Fragment>
|
||||
{this._renderReactions()}
|
||||
{this._renderDialogContent()}
|
||||
</React.Fragment>);
|
||||
return (
|
||||
<Fragment>
|
||||
{this._renderReactions()}
|
||||
{this._renderDialogContent()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Dialog from 'react-native-dialog';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
@@ -95,40 +92,39 @@ class InputDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Dialog.Container
|
||||
visible = { true }>
|
||||
<Dialog.Title>
|
||||
{ t(titleKey) }
|
||||
</Dialog.Title>
|
||||
{
|
||||
descriptionKey && (
|
||||
<Dialog.Description>
|
||||
{ t(descriptionKey) }
|
||||
</Dialog.Description>
|
||||
)
|
||||
}
|
||||
<Dialog.Input
|
||||
autoFocus = { true }
|
||||
onChangeText = { this._onChangeText }
|
||||
value = { this.state.fieldValue }
|
||||
{ ...this.props.textInputProps } />
|
||||
{
|
||||
messageKey && (
|
||||
<Dialog.Description
|
||||
style = { styles.formMessage }>
|
||||
{ t(messageKey) }
|
||||
</Dialog.Description>
|
||||
)
|
||||
}
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Cancel') }
|
||||
onPress = { this._onCancel } />
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Ok') }
|
||||
onPress = { this._onSubmitValue } />
|
||||
</Dialog.Container>
|
||||
</View>
|
||||
<Dialog.Container
|
||||
coverScreen = { false }
|
||||
visible = { true }>
|
||||
<Dialog.Title>
|
||||
{ t(titleKey) }
|
||||
</Dialog.Title>
|
||||
{
|
||||
descriptionKey && (
|
||||
<Dialog.Description>
|
||||
{ t(descriptionKey) }
|
||||
</Dialog.Description>
|
||||
)
|
||||
}
|
||||
<Dialog.Input
|
||||
autoFocus = { true }
|
||||
onChangeText = { this._onChangeText }
|
||||
value = { this.state.fieldValue }
|
||||
{ ...this.props.textInputProps } />
|
||||
{
|
||||
messageKey && (
|
||||
<Dialog.Description
|
||||
style = { styles.formMessage }>
|
||||
{ t(messageKey) }
|
||||
</Dialog.Description>
|
||||
)
|
||||
}
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Cancel') }
|
||||
onPress = { this._onCancel } />
|
||||
<Dialog.Button
|
||||
label = { t('dialog.Ok') }
|
||||
onPress = { this._onSubmitValue } />
|
||||
</Dialog.Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
@@ -75,7 +73,6 @@ export const bottomSheetStyles = {
|
||||
* Style for the container of the sheet.
|
||||
*/
|
||||
sheetContainer: {
|
||||
borderColor: 'red',
|
||||
alignItems: 'stretch',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
@@ -267,28 +264,3 @@ ColorSchemeRegistry.register('Dialog', {
|
||||
borderTopWidth: 1
|
||||
}
|
||||
});
|
||||
|
||||
ColorSchemeRegistry.register('SecurityDialog', {
|
||||
/**
|
||||
* Field on an input dialog.
|
||||
*/
|
||||
field: {
|
||||
borderBottomWidth: 1,
|
||||
borderColor: schemeColor('border'),
|
||||
color: schemeColor('text'),
|
||||
fontSize: 14,
|
||||
paddingBottom: 8
|
||||
},
|
||||
|
||||
text: {
|
||||
color: schemeColor('text'),
|
||||
fontSize: 14,
|
||||
marginTop: 8
|
||||
},
|
||||
|
||||
title: {
|
||||
color: schemeColor('text'),
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
import { assign, ReducerRegistry } from '../redux';
|
||||
|
||||
import { HIDE_DIALOG, OPEN_DIALOG } from './actionTypes';
|
||||
import {
|
||||
HIDE_DIALOG,
|
||||
HIDE_SHEET,
|
||||
OPEN_DIALOG,
|
||||
OPEN_SHEET
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Reduces redux actions which show or hide dialogs.
|
||||
@@ -32,6 +35,18 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
|
||||
component: action.component,
|
||||
componentProps: action.componentProps
|
||||
});
|
||||
|
||||
case HIDE_SHEET:
|
||||
return assign(state, {
|
||||
sheet: undefined,
|
||||
sheetProps: undefined
|
||||
});
|
||||
|
||||
case OPEN_SHEET:
|
||||
return assign(state, {
|
||||
sheet: action.component,
|
||||
sheetProps: action.componentProps
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -160,6 +160,12 @@ export const OVERFLOW_MENU_ENABLED = 'overflow-menu.enabled';
|
||||
*/
|
||||
export const PIP_ENABLED = 'pip.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the prejoin page should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const PREJOIN_PAGE_ENABLED = 'prejoinpage.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if raise hand feature should be enabled.
|
||||
* Default: enabled.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getDefaultHeaderHeight } from '@react-navigation/elements';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StatusBar
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { StyleType } from '../../styles';
|
||||
|
||||
@@ -33,6 +34,11 @@ type Props = {
|
||||
*/
|
||||
hasTabNavigator: boolean,
|
||||
|
||||
/**
|
||||
* Is the screen presented as a modal?
|
||||
*/
|
||||
isModalPresentation: boolean,
|
||||
|
||||
/**
|
||||
* Additional style to be appended to the KeyboardAvoidingView.
|
||||
*/
|
||||
@@ -45,26 +51,37 @@ const JitsiKeyboardAvoidingView = (
|
||||
contentContainerStyle,
|
||||
hasTabNavigator,
|
||||
hasBottomTextInput,
|
||||
isModalPresentation,
|
||||
style
|
||||
}: Props) => {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const frame = useSafeAreaFrame();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [ bottomPadding, setBottomPadding ] = useState(insets.bottom);
|
||||
const [ topPadding, setTopPadding ] = useState(insets.top);
|
||||
|
||||
useEffect(() => {
|
||||
// This useEffect is needed because insets are undefined at first for some reason
|
||||
// https://github.com/th3rdwave/react-native-safe-area-context/issues/54
|
||||
setBottomPadding(insets.bottom);
|
||||
setTopPadding(insets.top);
|
||||
}, [ insets.bottom, insets.top ]);
|
||||
|
||||
}, [ insets.bottom ]);
|
||||
const headerHeight = getDefaultHeaderHeight(frame, isModalPresentation, topPadding);
|
||||
|
||||
// Notch devices have in general a header height between 103 and 106px
|
||||
const topNotchDevice = headerHeight > 100;
|
||||
const deviceHeight = topNotchDevice ? headerHeight - 50 : headerHeight;
|
||||
const tabNavigatorPadding
|
||||
= hasTabNavigator ? headerHeight : 0;
|
||||
= hasTabNavigator ? deviceHeight : 0;
|
||||
const noNotchDevicePadding = bottomPadding || 10;
|
||||
const iosVerticalOffset
|
||||
= headerHeight + noNotchDevicePadding + tabNavigatorPadding;
|
||||
= deviceHeight + noNotchDevicePadding + tabNavigatorPadding;
|
||||
const androidVerticalOffset = hasBottomTextInput
|
||||
? headerHeight + StatusBar.currentHeight : headerHeight;
|
||||
? deviceHeight + StatusBar.currentHeight : deviceHeight;
|
||||
|
||||
// Tells the view what to do with taps
|
||||
const shouldSetResponse = useCallback(() => true);
|
||||
const onRelease = useCallback(() => Keyboard.dismiss());
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
@@ -76,6 +93,8 @@ const JitsiKeyboardAvoidingView = (
|
||||
? iosVerticalOffset
|
||||
: androidVerticalOffset
|
||||
}
|
||||
onResponderRelease = { onRelease }
|
||||
onStartShouldSetResponder = { shouldSetResponse }
|
||||
style = { style }>
|
||||
{ children }
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
@@ -37,6 +37,11 @@ type Props = {
|
||||
*/
|
||||
hasTabNavigator?: boolean,
|
||||
|
||||
/**
|
||||
* Is the screen presented as a modal?
|
||||
*/
|
||||
isModalPresentation?: boolean,
|
||||
|
||||
/**
|
||||
* Insets for the SafeAreaView.
|
||||
*/
|
||||
@@ -54,7 +59,8 @@ const JitsiScreen = ({
|
||||
footerComponent,
|
||||
hasTabNavigator = false,
|
||||
hasBottomTextInput = false,
|
||||
safeAreaInsets = [ 'bottom', 'left', 'right' ],
|
||||
isModalPresentation = true,
|
||||
safeAreaInsets = [ 'left', 'right' ],
|
||||
style
|
||||
}: Props) => (
|
||||
<View
|
||||
@@ -63,13 +69,14 @@ const JitsiScreen = ({
|
||||
contentContainerStyle = { contentContainerStyle }
|
||||
hasBottomTextInput = { hasBottomTextInput }
|
||||
hasTabNavigator = { hasTabNavigator }
|
||||
isModalPresentation = { isModalPresentation }
|
||||
style = { style }>
|
||||
<SafeAreaView
|
||||
edges = { safeAreaInsets }
|
||||
style = { styles.safeArea }>
|
||||
{ children }
|
||||
{children}
|
||||
</SafeAreaView>
|
||||
{ footerComponent && footerComponent() }
|
||||
{footerComponent && footerComponent()}
|
||||
</JitsiKeyboardAvoidingView>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -178,20 +178,24 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case SET_LOCAL_PARTICIPANT_RECORDING_STATUS: {
|
||||
const state = store.getState();
|
||||
const { recording } = action;
|
||||
const localId = getLocalParticipant(store.getState())?.id;
|
||||
const localId = getLocalParticipant(state)?.id;
|
||||
const { localRecording } = state['features/base/config'];
|
||||
|
||||
store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
if (localRecording.notifyAllParticipants) {
|
||||
store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: localId,
|
||||
local: true,
|
||||
localRecording: recording
|
||||
}));
|
||||
id: localId,
|
||||
local: true,
|
||||
localRecording: recording
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,13 @@ const blacklist = [ '__proto__', 'constructor', 'prototype' ];
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function parseURLParams(
|
||||
url: URL,
|
||||
url: URL | string,
|
||||
dontParse: boolean = false,
|
||||
source: string = 'hash'): Object {
|
||||
if (typeof url === 'string') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
url = new URL(url);
|
||||
}
|
||||
const paramStr = source === 'search' ? url.search : url.hash;
|
||||
const params = {};
|
||||
const paramParts = (paramStr && paramStr.substr(1).split('&')) || [];
|
||||
|
||||
@@ -524,7 +524,7 @@ export function urlObjectToString(o: Object): ?string {
|
||||
// query/search
|
||||
|
||||
// Web's ExternalAPI jwt and lang
|
||||
const { jwt, lang } = o;
|
||||
const { jwt, lang, release } = o;
|
||||
|
||||
const search = new URLSearchParams(url.search);
|
||||
|
||||
@@ -538,6 +538,10 @@ export function urlObjectToString(o: Object): ?string {
|
||||
search.set('lang', lang || defaultLanguage);
|
||||
}
|
||||
|
||||
if (release) {
|
||||
search.set('release', release);
|
||||
}
|
||||
|
||||
const searchString = search.toString();
|
||||
|
||||
if (searchString) {
|
||||
@@ -603,3 +607,20 @@ export function addHashParamsToURL(url: URL, hashParamsToAdd: Object = {}) {
|
||||
export function getDecodedURI(uri: string) {
|
||||
return decodeURI(uri.replace(/^https?:\/\//i, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new param to a url string. Checks whether to use '?' or '&' as a separator (checks for already existing params).
|
||||
*
|
||||
* @param {string} url - The url to modify.
|
||||
* @param {string} name - The param name to add.
|
||||
* @param {string} value - The value for the param.
|
||||
*
|
||||
* @returns {string} - The modified url.
|
||||
*/
|
||||
export function appendURLParam(url: string, name: string, value: string) {
|
||||
const newUrl = new URL(url);
|
||||
|
||||
newUrl.searchParams.append(name, value);
|
||||
|
||||
return newUrl.toString();
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ export default {
|
||||
borderTopColor: 'rgb(209, 219, 231)',
|
||||
borderTopWidth: 1,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: BoxModel.padding
|
||||
paddingBottom: '4%',
|
||||
paddingHorizontal: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
inputField: {
|
||||
|
||||
@@ -104,6 +104,7 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
||||
shouldShow: false
|
||||
};
|
||||
|
||||
this.isEdge = /Edg(e)?/.test(navigator.userAgent);
|
||||
this._onClosePressed = this._onClosePressed.bind(this);
|
||||
this._onInstallExtensionClick = this._onInstallExtensionClick.bind(this);
|
||||
this._shouldNotRender = this._shouldNotRender.bind(this);
|
||||
@@ -196,8 +197,10 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onInstallExtensionClick() {
|
||||
const { edgeUrl, url } = this.props.bannerCfg;
|
||||
|
||||
sendAnalytics(createChromeExtensionBannerEvent(true));
|
||||
window.open(this.props.bannerCfg.url);
|
||||
window.open(this.isEdge && edgeUrl ? edgeUrl : url);
|
||||
this.setState({ closePressed: true });
|
||||
}
|
||||
|
||||
@@ -264,7 +267,7 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
||||
|
||||
return null;
|
||||
}
|
||||
const { t } = this.props;
|
||||
const { bannerCfg, t } = this.props;
|
||||
const mainClassNames = this.props.conference
|
||||
? 'chrome-extension-banner chrome-extension-banner__pos_in_meeting'
|
||||
: 'chrome-extension-banner';
|
||||
@@ -306,7 +309,10 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
||||
<div
|
||||
className = 'chrome-extension-banner__button-text'
|
||||
id = 'chrome-extension-banner__button-text'>
|
||||
{ t('chromeExtensionBanner.buttonText') }
|
||||
{ t(this.isEdge && bannerCfg.edgeUrl
|
||||
? 'chromeExtensionBanner.buttonTextEdge'
|
||||
: 'chromeExtensionBanner.buttonText')
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,11 +22,14 @@ import {
|
||||
} from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { startKnocking } from '../../../lobby/actions.any';
|
||||
import { KnockingParticipantList } from '../../../lobby/components/native';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { shouldEnableAutoKnock } from '../../../mobile/navigation/functions';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { setPictureInPictureEnabled } from '../../../mobile/picture-in-picture';
|
||||
import { Captions } from '../../../subtitles';
|
||||
import { setToolboxVisible } from '../../../toolbox/actions';
|
||||
import { Toolbox } from '../../../toolbox/components/native';
|
||||
@@ -61,11 +64,6 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_brandingStyles: Object,
|
||||
|
||||
/**
|
||||
* Branding image background.
|
||||
*/
|
||||
_brandingImageBackgroundUrl: string,
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*/
|
||||
@@ -104,6 +102,11 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_largeVideoParticipantId: string,
|
||||
|
||||
/**
|
||||
* Local participant's display name.
|
||||
*/
|
||||
_localParticipantDisplayName: string,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*/
|
||||
@@ -120,6 +123,11 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_toolboxVisible: boolean,
|
||||
|
||||
/**
|
||||
* Indicates if we should auto-knock.
|
||||
*/
|
||||
_shouldEnableAutoKnock: boolean,
|
||||
|
||||
/**
|
||||
* Indicates whether the lobby screen should be visible.
|
||||
*/
|
||||
@@ -184,6 +192,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
*/
|
||||
componentDidMount() {
|
||||
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,10 +201,18 @@ class Conference extends AbstractConference<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
const { _showLobby } = this.props;
|
||||
const {
|
||||
_shouldEnableAutoKnock,
|
||||
_showLobby,
|
||||
dispatch
|
||||
} = this.props;
|
||||
|
||||
if (!prevProps._showLobby && _showLobby) {
|
||||
navigate(screen.lobby.root);
|
||||
|
||||
if (_shouldEnableAutoKnock) {
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
}
|
||||
|
||||
if (prevProps._showLobby && !_showLobby) {
|
||||
@@ -216,6 +233,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
|
||||
clearTimeout(this._expandedLabelTimeout.current);
|
||||
setPictureInPictureEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +244,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_brandingImageBackgroundUrl,
|
||||
_brandingStyles,
|
||||
_fullscreenEnabled
|
||||
} = this.props;
|
||||
@@ -237,8 +254,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
styles.conference,
|
||||
_brandingStyles
|
||||
] }>
|
||||
<BrandingImageBackground
|
||||
uri = { _brandingImageBackgroundUrl } />
|
||||
<BrandingImageBackground />
|
||||
<StatusBar
|
||||
barStyle = 'light-content'
|
||||
hidden = { _fullscreenEnabled }
|
||||
@@ -520,7 +536,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
function _mapStateToProps(state) {
|
||||
const { isOpen } = state['features/participants-pane'];
|
||||
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
|
||||
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
|
||||
const { backgroundColor } = state['features/dynamic-branding'];
|
||||
const participantCount = getParticipantCount(state);
|
||||
const brandingStyles = backgroundColor ? {
|
||||
backgroundColor
|
||||
@@ -530,7 +546,6 @@ function _mapStateToProps(state) {
|
||||
...abstractMapStateToProps(state),
|
||||
_aspectRatio: aspectRatio,
|
||||
_brandingStyles: brandingStyles,
|
||||
_brandingImageBackgroundUrl: backgroundImageUrl,
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
_connecting: isConnecting(state),
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
@@ -540,6 +555,7 @@ function _mapStateToProps(state) {
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||
_reducedUI: reducedUI,
|
||||
_shouldEnableAutoKnock: shouldEnableAutoKnock(state),
|
||||
_showLobby: getIsLobbyVisible(state),
|
||||
_toolboxVisible: isToolboxVisible(state)
|
||||
};
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { getFeatureFlag, INVITE_ENABLED } from '../../../base/flags';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconAddPeople } from '../../../base/icons';
|
||||
import { getParticipantCountWithFake } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { doInvitePeople } from '../../../invite/actions.native';
|
||||
|
||||
@@ -35,11 +31,6 @@ type Props = {
|
||||
*/
|
||||
_isLonelyMeeting: boolean,
|
||||
|
||||
/**
|
||||
* Color schemed styles of the component.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
@@ -76,7 +67,6 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
_isInBreakoutRoom,
|
||||
_isInviteFunctionsDiabled,
|
||||
_isLonelyMeeting,
|
||||
_styles,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
@@ -86,29 +76,18 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<View style = { styles.lonelyMeetingContainer }>
|
||||
<Text
|
||||
style = { [
|
||||
styles.lonelyMessage,
|
||||
_styles.lonelyMessage
|
||||
] }>
|
||||
<Text style = { styles.lonelyMessage }>
|
||||
{ t('lonelyMeetingExperience.youAreAlone') }
|
||||
</Text>
|
||||
{ !_isInviteFunctionsDiabled && !_isInBreakoutRoom && (
|
||||
<TouchableOpacity
|
||||
onPress = { this._onPress }
|
||||
style = { [
|
||||
styles.lonelyButton,
|
||||
_styles.lonelyButton
|
||||
] }>
|
||||
style = { styles.lonelyButton }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconAddPeople }
|
||||
style = { styles.lonelyButtonComponents } />
|
||||
<Text
|
||||
style = { [
|
||||
styles.lonelyButtonComponents,
|
||||
_styles.lonelyMessage
|
||||
] }>
|
||||
<Text style = { styles.lonelyButtonComponents }>
|
||||
{ t('lonelyMeetingExperience.button') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -117,8 +96,6 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_onPress: () => void;
|
||||
|
||||
/**
|
||||
* Callback for the onPress function of the button.
|
||||
*
|
||||
@@ -136,7 +113,7 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state): $Shape<Props> {
|
||||
function _mapStateToProps(state) {
|
||||
const { disableInviteFunctions } = state['features/base/config'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const flag = getFeatureFlag(state, INVITE_ENABLED, true);
|
||||
@@ -145,8 +122,7 @@ function _mapStateToProps(state): $Shape<Props> {
|
||||
return {
|
||||
_isInBreakoutRoom,
|
||||
_isInviteFunctionsDiabled: !flag || disableInviteFunctions,
|
||||
_isLonelyMeeting: conference && getParticipantCountWithFake(state) === 1,
|
||||
_styles: ColorSchemeRegistry.get(state, 'Conference')
|
||||
_isLonelyMeeting: conference && getParticipantCountWithFake(state) === 1
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, SafeAreaView, View } from 'react-native';
|
||||
import { Text, View } from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator, TintedView } from '../../../../base/react';
|
||||
import { isLocalVideoTrackDesktop } from '../../../../base/tracks';
|
||||
import { setPictureInPictureEnabled } from '../../../../mobile/picture-in-picture/functions';
|
||||
import { setIsCarmode } from '../../../../video-layout/actions';
|
||||
import ConferenceTimer from '../../ConferenceTimer';
|
||||
import { isConnecting } from '../../functions';
|
||||
@@ -15,8 +17,6 @@ import MicrophoneButton from './MicrophoneButton';
|
||||
import SoundDeviceButton from './SoundDeviceButton';
|
||||
import TitleBar from './TitleBar';
|
||||
import styles from './styles';
|
||||
import { isLocalVideoTrackDesktop } from '../../../../base/tracks';
|
||||
import { setPictureInPictureDisabled } from '../../../../mobile/picture-in-picture/functions';
|
||||
|
||||
/**
|
||||
* Implements the carmode tab.
|
||||
@@ -31,14 +31,14 @@ const CarmodeTab = (): JSX.Element => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setIsCarmode(true));
|
||||
setPictureInPictureDisabled(true);
|
||||
setPictureInPictureEnabled(false);
|
||||
|
||||
return () => {
|
||||
dispatch(setIsCarmode(false));
|
||||
if (!isSharing) {
|
||||
setPictureInPictureDisabled(false);
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openDialog } from '../../../../base/dialog/actions';
|
||||
import { openSheet } from '../../../../base/dialog/actions';
|
||||
import AudioRoutePickerDialog from '../../../../mobile/audio-mode/components/AudioRoutePickerDialog';
|
||||
|
||||
import AudioIcon from './AudioIcon';
|
||||
@@ -19,7 +19,7 @@ const SelectSoundDevice = () : JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() =>
|
||||
dispatch(openDialog(AudioRoutePickerDialog))
|
||||
dispatch(openSheet(AudioRoutePickerDialog))
|
||||
, [ dispatch ]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { getConferenceName } from '../../../../base/conference/functions';
|
||||
import { ConnectionIndicator } from '../../../../connection-indicator';
|
||||
import { getFeatureFlag, MEETING_NAME_ENABLED } from '../../../../base/flags';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../../base/redux';;
|
||||
import { RecordingLabel } from '../../../../recording';
|
||||
import { VideoQualityLabel } from '../../../../video-quality';
|
||||
|
||||
import { getLocalParticipant } from '../../../../base/participants';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -32,35 +35,42 @@ type Props = {
|
||||
* @param {Props} props - The React props passed to this component.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const TitleBar = (props: Props) : JSX.Element => (<>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.titleBarWrapper }>
|
||||
const TitleBar = (props: Props) : JSX.Element => {
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const localParticipantId = localParticipant?.id;
|
||||
|
||||
return (<>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.roomNameWrapper }>
|
||||
<View style = { styles.qualityLabelContainer }>
|
||||
<VideoQualityLabel />
|
||||
</View>
|
||||
|
||||
<View style = { styles.headerLabels }>
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
</View>
|
||||
|
||||
{
|
||||
props._meetingNameEnabled
|
||||
&& <View style = { styles.roomNameView }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.roomName }>
|
||||
{props._meetingName}
|
||||
</Text>
|
||||
style = { styles.titleBarWrapper }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.roomNameWrapper }>
|
||||
<View style = { styles.qualityLabelContainer }>
|
||||
<VideoQualityLabel />
|
||||
</View>
|
||||
}
|
||||
<ConnectionIndicator
|
||||
participantId = { localParticipantId }
|
||||
iconStyle = { styles.connectionIndicatorIcon } />
|
||||
<View style = { styles.headerLabels }>
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
</View>
|
||||
|
||||
{
|
||||
props._meetingNameEnabled
|
||||
&& <View style = { styles.roomNameView }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.roomName }>
|
||||
{props._meetingName}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>);
|
||||
</>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
|
||||
@@ -203,5 +203,9 @@ export default {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginBottom: 32,
|
||||
...BaseTheme.typography.bodyShortRegularLarge
|
||||
},
|
||||
|
||||
connectionIndicatorIcon: {
|
||||
fontSize: 20
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { fixAndroidViewClipping } from '../../../base/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
@@ -81,6 +80,7 @@ export default {
|
||||
|
||||
lonelyButton: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.action01,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[6],
|
||||
@@ -89,6 +89,7 @@ export default {
|
||||
},
|
||||
|
||||
lonelyButtonComponents: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginHorizontal: 6
|
||||
},
|
||||
|
||||
@@ -99,6 +100,7 @@ export default {
|
||||
},
|
||||
|
||||
lonelyMessage: {
|
||||
color: BaseTheme.palette.text01,
|
||||
paddingVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
@@ -230,13 +232,3 @@ export default {
|
||||
paddingLeft: BaseTheme.spacing[2]
|
||||
}
|
||||
};
|
||||
|
||||
ColorSchemeRegistry.register('Conference', {
|
||||
lonelyButton: {
|
||||
backgroundColor: schemeColor('inviteButtonBackground')
|
||||
},
|
||||
|
||||
lonelyMessage: {
|
||||
color: schemeColor('onVideoText')
|
||||
}
|
||||
});
|
||||
|
||||
@@ -32,6 +32,11 @@ export type Props = {
|
||||
*/
|
||||
participantId: string,
|
||||
|
||||
/**
|
||||
* Custom icon style.
|
||||
*/
|
||||
iconStyle?: Object,
|
||||
|
||||
/**
|
||||
* The source name of the track.
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
}}>
|
||||
<BaseIndicator
|
||||
icon = { IconConnectionActive }
|
||||
iconStyle = { iconStyle } />
|
||||
iconStyle = { this.props.iconStyle || iconStyle } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { NativeModules } from 'react-native';
|
||||
|
||||
const { Dropbox } = NativeModules;
|
||||
|
||||
import { setPictureInPictureDisabled } from '../mobile/picture-in-picture/functions';
|
||||
import { setPictureInPictureEnabled } from '../mobile/picture-in-picture/functions';
|
||||
|
||||
/**
|
||||
* Action to authorize the Jitsi Recording app in dropbox.
|
||||
@@ -13,12 +13,12 @@ import { setPictureInPictureDisabled } from '../mobile/picture-in-picture/functi
|
||||
* access token or rejected with an error.
|
||||
*/
|
||||
export async function _authorizeDropbox(): Promise<Object> {
|
||||
setPictureInPictureDisabled(true);
|
||||
setPictureInPictureEnabled(false);
|
||||
|
||||
try {
|
||||
return await Dropbox.authorize();
|
||||
} finally {
|
||||
setPictureInPictureDisabled(false);
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ import React from 'react';
|
||||
import { Image } from 'react-native';
|
||||
import { SvgUri } from 'react-native-svg';
|
||||
|
||||
// @ts-ignore
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -21,13 +24,25 @@ const BrandingImageBackground: React.FC<Props> = ({ uri }:Props) => {
|
||||
|
||||
let backgroundImage;
|
||||
|
||||
if (!uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (imageType?.includes('.svg')) {
|
||||
backgroundImage
|
||||
= (
|
||||
<SvgUri
|
||||
height = '100%'
|
||||
|
||||
// Force uniform scaling.
|
||||
// Align the <min-x> of the element's viewBox
|
||||
// with the smallest X value of the viewport.
|
||||
// Align the <min-y> of the element's viewBox
|
||||
// with the smallest Y value of the viewport.
|
||||
preserveAspectRatio = 'xMinYMin'
|
||||
style = { styles.brandingImageBackgroundSvg }
|
||||
uri = { imgSrc }
|
||||
viewBox = '0 0 400 650'
|
||||
width = '100%' />
|
||||
);
|
||||
} else {
|
||||
@@ -42,4 +57,20 @@ const BrandingImageBackground: React.FC<Props> = ({ uri }:Props) => {
|
||||
return backgroundImage;
|
||||
};
|
||||
|
||||
export default BrandingImageBackground;
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code DialInLink} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: any) {
|
||||
const { backgroundImageUrl } = state['features/dynamic-branding'];
|
||||
|
||||
return {
|
||||
uri: backgroundImageUrl
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(BrandingImageBackground);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconArrowBack } from '../../../base/icons';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
@@ -27,11 +24,6 @@ type Props = {
|
||||
*/
|
||||
_documentUrl: string,
|
||||
|
||||
/**
|
||||
* Color schemed style of the header component.
|
||||
*/
|
||||
_headerStyles: Object,
|
||||
|
||||
/**
|
||||
* Default prop for navigation between screen components(React Navigation).
|
||||
*/
|
||||
@@ -97,8 +89,6 @@ class SharedDocument extends PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_renderLoading: () => React$Component<any>;
|
||||
|
||||
/**
|
||||
* Renders the loading indicator.
|
||||
*
|
||||
@@ -126,8 +116,7 @@ export function _mapStateToProps(state: Object) {
|
||||
const documentUrl = getSharedDocumentUrl(state);
|
||||
|
||||
return {
|
||||
_documentUrl: documentUrl,
|
||||
_headerStyles: ColorSchemeRegistry.get(state, 'Header')
|
||||
_documentUrl: documentUrl
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@ import { Human, Config, FaceResult } from '@vladmandic/human';
|
||||
|
||||
import { DETECTION_TYPES, FACE_DETECTION_SCORE_THRESHOLD, FACE_EXPRESSIONS_NAMING_MAPPING } from './constants';
|
||||
|
||||
type Detection = {
|
||||
detections: Array<FaceResult>,
|
||||
threshold?: number
|
||||
};
|
||||
|
||||
type DetectInput = {
|
||||
image: ImageBitmap | ImageData,
|
||||
threshold: number
|
||||
@@ -21,20 +16,22 @@ type FaceBox = {
|
||||
|
||||
type InitInput = {
|
||||
baseUrl: string,
|
||||
detectionTypes: string[],
|
||||
maxFacesDetected?: number
|
||||
detectionTypes: string[]
|
||||
}
|
||||
|
||||
type DetectOutput = {
|
||||
faceExpression?: string,
|
||||
faceBox?: FaceBox
|
||||
faceBox?: FaceBox,
|
||||
faceCount: number
|
||||
};
|
||||
|
||||
export interface FaceLandmarksHelper {
|
||||
getFaceBox({ detections, threshold }: Detection): FaceBox | undefined;
|
||||
getFaceExpression({ detections }: Detection): string | undefined;
|
||||
getFaceBox(detections: Array<FaceResult>, threshold: number): FaceBox | undefined;
|
||||
getFaceExpression(detections: Array<FaceResult>): string | undefined;
|
||||
getFaceCount(detections : Array<FaceResult>): number;
|
||||
getDetections(image: ImageBitmap | ImageData): Promise<Array<FaceResult>>;
|
||||
init(): Promise<void>;
|
||||
detect({ image, threshold } : DetectInput): Promise<DetectOutput | undefined>;
|
||||
detect({ image, threshold } : DetectInput): Promise<DetectOutput>;
|
||||
getDetectionInProgress(): boolean;
|
||||
}
|
||||
|
||||
@@ -45,7 +42,6 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
protected human: Human | undefined;
|
||||
protected faceDetectionTypes: string[];
|
||||
protected baseUrl: string;
|
||||
protected maxFacesDetected?: number;
|
||||
private detectionInProgress = false;
|
||||
private lastValidFaceBox: FaceBox | undefined;
|
||||
/**
|
||||
@@ -66,7 +62,7 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
enabled: false,
|
||||
rotation: false,
|
||||
modelPath: 'blazeface-front.json',
|
||||
maxDetected: 4
|
||||
maxDetected: 20
|
||||
},
|
||||
mesh: { enabled: false },
|
||||
iris: { enabled: false },
|
||||
@@ -82,10 +78,9 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
segmentation: { enabled: false }
|
||||
};
|
||||
|
||||
constructor({ baseUrl, detectionTypes, maxFacesDetected }: InitInput) {
|
||||
constructor({ baseUrl, detectionTypes }: InitInput) {
|
||||
this.faceDetectionTypes = detectionTypes;
|
||||
this.baseUrl = baseUrl;
|
||||
this.maxFacesDetected = maxFacesDetected;
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -102,10 +97,6 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
if (this.faceDetectionTypes.length > 0 && this.config.face) {
|
||||
this.config.face.enabled = true
|
||||
}
|
||||
|
||||
if (this.maxFacesDetected && this.config.face?.detector) {
|
||||
this.config.face.detector.maxDetected = this.maxFacesDetected;
|
||||
}
|
||||
|
||||
if (this.faceDetectionTypes.includes(DETECTION_TYPES.FACE_BOX) && this.config.face?.detector) {
|
||||
this.config.face.detector.enabled = true;
|
||||
@@ -126,15 +117,15 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
}
|
||||
}
|
||||
|
||||
getFaceBox({ detections, threshold }: Detection): FaceBox | undefined {
|
||||
if (!detections.length) {
|
||||
getFaceBox(detections: Array<FaceResult>, threshold: number): FaceBox | undefined {
|
||||
if (this.getFaceCount(detections) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const faceBox: FaceBox = {
|
||||
// normalize to percentage based
|
||||
left: Math.round(Math.min(...detections.map(d => d.boxRaw[0])) * 100),
|
||||
right: Math.round(Math.max(...detections.map(d => d.boxRaw[0] + d.boxRaw[2])) * 100)
|
||||
left: Math.round(detections[0].boxRaw[0] * 100),
|
||||
right: Math.round((detections[0].boxRaw[0] + detections[0].boxRaw[2]) * 100)
|
||||
};
|
||||
|
||||
faceBox.width = Math.round(faceBox.right - faceBox.left);
|
||||
@@ -148,15 +139,27 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
return faceBox;
|
||||
}
|
||||
|
||||
getFaceExpression({ detections }: Detection): string | undefined {
|
||||
if (detections[0]?.emotion) {
|
||||
return FACE_EXPRESSIONS_NAMING_MAPPING[detections[0]?.emotion[0].emotion];
|
||||
getFaceExpression(detections: Array<FaceResult>): string | undefined {
|
||||
if (this.getFaceCount(detections) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detections[0].emotion) {
|
||||
return FACE_EXPRESSIONS_NAMING_MAPPING[detections[0].emotion[0].emotion];
|
||||
}
|
||||
}
|
||||
|
||||
async getDetections(image: ImageBitmap | ImageData) {
|
||||
if (!this.human) {
|
||||
return;
|
||||
getFaceCount(detections: Array<FaceResult> | undefined): number {
|
||||
if (detections) {
|
||||
return detections.length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async getDetections(image: ImageBitmap | ImageData): Promise<Array<FaceResult>> {
|
||||
if (!this.human || !this.faceDetectionTypes.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this.human.tf.engine().startScope();
|
||||
@@ -169,39 +172,42 @@ export class HumanHelper implements FaceLandmarksHelper {
|
||||
return detections.filter(detection => detection.score > FACE_DETECTION_SCORE_THRESHOLD);
|
||||
}
|
||||
|
||||
public async detect({ image, threshold } : DetectInput): Promise<DetectOutput | undefined> {
|
||||
public async detect({ image, threshold } : DetectInput): Promise<DetectOutput> {
|
||||
let detections;
|
||||
let faceExpression;
|
||||
let faceBox;
|
||||
|
||||
this.detectionInProgress = true;
|
||||
|
||||
if (this.faceDetectionTypes.includes(DETECTION_TYPES.FACE_EXPRESSIONS)) {
|
||||
detections = await this.getDetections(image);
|
||||
detections = await this.getDetections(image);
|
||||
|
||||
if (detections) {
|
||||
faceExpression = this.getFaceExpression({ detections });
|
||||
}
|
||||
if (this.faceDetectionTypes.includes(DETECTION_TYPES.FACE_EXPRESSIONS)) {
|
||||
faceExpression = this.getFaceExpression(detections);
|
||||
}
|
||||
|
||||
if (this.faceDetectionTypes.includes(DETECTION_TYPES.FACE_BOX)) {
|
||||
if (!detections) {
|
||||
detections = await this.getDetections(image);
|
||||
//if more than one face is detected the face centering will be disabled.
|
||||
if (this.getFaceCount(detections) > 1 ) {
|
||||
this.faceDetectionTypes.splice(this.faceDetectionTypes.indexOf(DETECTION_TYPES.FACE_BOX), 1);
|
||||
|
||||
//face-box for re-centering
|
||||
faceBox = {
|
||||
left: 0,
|
||||
right: 100,
|
||||
width: 100,
|
||||
};
|
||||
} else {
|
||||
faceBox = this.getFaceBox(detections, threshold);
|
||||
}
|
||||
|
||||
if(detections) {
|
||||
faceBox = this.getFaceBox({
|
||||
detections,
|
||||
threshold
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.detectionInProgress = false;
|
||||
|
||||
return {
|
||||
faceExpression,
|
||||
faceBox
|
||||
faceBox,
|
||||
faceCount: this.getFaceCount(detections)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,8 +140,7 @@ export function loadWorker() {
|
||||
worker.postMessage({
|
||||
type: INIT_WORKER,
|
||||
baseUrl,
|
||||
detectionTypes,
|
||||
maxFacesDetected: faceLandmarks?.maxFacesDetected
|
||||
detectionTypes
|
||||
});
|
||||
|
||||
dispatch(startFaceLandmarksDetection());
|
||||
|
||||
@@ -13,7 +13,7 @@ onmessage = async function(message: MessageEvent<any>) {
|
||||
|
||||
const detections = await helper.detect(message.data);
|
||||
|
||||
if (detections && (detections.faceBox || detections.faceExpression)) {
|
||||
if (detections && (detections.faceBox || detections.faceExpression || detections.faceCount)) {
|
||||
self.postMessage(detections);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useSelector } from 'react-redux';
|
||||
import { IconPinParticipant } from '../../../base/icons';
|
||||
import { getParticipantById } from '../../../base/participants';
|
||||
import { BaseIndicator } from '../../../base/react';
|
||||
import { getPinnedActiveParticipants, isStageFilmstripEnabled } from '../../functions.web';
|
||||
import { getPinnedActiveParticipants, isStageFilmstripAvailable } from '../../functions.web';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link PinnedIndicator}.
|
||||
@@ -54,7 +54,7 @@ const PinnedIndicator = ({
|
||||
participantId,
|
||||
tooltipPosition
|
||||
}: Props) => {
|
||||
const stageFilmstrip = useSelector(isStageFilmstripEnabled);
|
||||
const stageFilmstrip = useSelector(isStageFilmstripAvailable);
|
||||
const pinned = useSelector(state => getParticipantById(state, participantId))?.pinned;
|
||||
const isPinned = useSelector(getPinnedActiveParticipants).find(p => p.participantId === participantId);
|
||||
const styles = useStyles();
|
||||
|
||||
@@ -230,7 +230,6 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||
data = { inviteItems }
|
||||
horizontal = { true }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
keyboardShouldPersistTaps = 'always'
|
||||
renderItem = { this._renderInvitedItem } />
|
||||
</View> }
|
||||
<View style = { styles.resultList }>
|
||||
@@ -239,7 +238,6 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||
data = { selectableItems }
|
||||
extraData = { inviteItems }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
keyboardShouldPersistTaps = 'always'
|
||||
renderItem = { this._renderItem } />
|
||||
</View>
|
||||
</JitsiScreen>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Linking, Platform, View } from 'react-native';
|
||||
import { Linking, View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { openDialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { IconClose } from '../../../../base/icons';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator } from '../../../../base/react';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import HeaderNavigationButton
|
||||
from '../../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { getDialInfoPageURLForURIString } from '../../../functions';
|
||||
|
||||
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
|
||||
import styles, { INDICATOR_COLOR } from './styles';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
dispatch: Dispatch<any>,
|
||||
@@ -65,28 +63,9 @@ class DialInSummary extends PureComponent<Props> {
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { navigation, t } = this.props;
|
||||
const onNavigationClose = () => {
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
navigation.setOptions({
|
||||
headerLeft: () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
label = { t('dialog.close') }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onPress = { onNavigationClose } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onPress = { onNavigationClose }
|
||||
src = { IconClose } />
|
||||
);
|
||||
}
|
||||
headerTitle: t('dialIn.screenTitle')
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ import { i18next } from '../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
import { parseURIString } from '../base/util';
|
||||
import {
|
||||
appendURLParam,
|
||||
parseURIString,
|
||||
parseURLParams
|
||||
} from '../base/util';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
import { getDialInConferenceID, getDialInNumbers } from './_utils';
|
||||
@@ -22,6 +26,13 @@ import logger from './logger';
|
||||
declare var $: Function;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
export const sharingFeatures = {
|
||||
email: 'email',
|
||||
url: 'url',
|
||||
dialIn: 'dial-in',
|
||||
embed: 'embed'
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends an ajax request to check if the phone number can be called.
|
||||
*
|
||||
@@ -242,7 +253,7 @@ export function getInviteTextiOS({
|
||||
invite += t('info.inviteTextiOSInviteUrl', { inviteUrl });
|
||||
invite += ' ';
|
||||
|
||||
if (shouldDisplayDialIn(dialIn)) {
|
||||
if (shouldDisplayDialIn(dialIn) && isSharingEnabled(sharingFeatures.dialIn)) {
|
||||
invite += t('info.inviteTextiOSPhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: dialIn.conferenceID,
|
||||
@@ -291,7 +302,7 @@ export function getInviteText({
|
||||
invite = `${invite}\n${liveStream}`;
|
||||
}
|
||||
|
||||
if (shouldDisplayDialIn(dialIn)) {
|
||||
if (shouldDisplayDialIn(dialIn) && isSharingEnabled(sharingFeatures.dialIn)) {
|
||||
const dial = t('info.invitePhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: dialIn.conferenceID
|
||||
@@ -596,7 +607,7 @@ export function getDialInfoPageURL(state: Object, roomName: ?string) {
|
||||
|
||||
const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/${DIAL_IN_INFO_PAGE_PATH_NAME}`;
|
||||
|
||||
return `${url}?room=${room}`;
|
||||
return appendURLParam(url, 'room', room);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -611,8 +622,15 @@ export function getDialInfoPageURLForURIString(
|
||||
return undefined;
|
||||
}
|
||||
const { protocol, host, contextRoot, room } = parseURIString(uri);
|
||||
let url = `${protocol}//${host}${contextRoot}${DIAL_IN_INFO_PAGE_PATH_NAME}`;
|
||||
|
||||
return `${protocol}//${host}${contextRoot}${DIAL_IN_INFO_PAGE_PATH_NAME}?room=${room}`;
|
||||
url = appendURLParam(url, 'room', room);
|
||||
|
||||
const { release } = parseURLParams(uri, true, 'search');
|
||||
|
||||
release && (url = appendURLParam(url, 'release', release));
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -790,13 +808,6 @@ export async function executeDialOutStatusRequest(url: string, reqId: string) {
|
||||
return res.ok ? json : Promise.reject(json);
|
||||
}
|
||||
|
||||
export const sharingFeatures = {
|
||||
email: 'email',
|
||||
url: 'url',
|
||||
dialIn: 'dial-in',
|
||||
embed: 'embed'
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if a specific sharing feature is enabled in interface configuration.
|
||||
*
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
import { BrandingImageBackground } from '../../../dynamic-branding';
|
||||
import { LargeVideo } from '../../../large-video/components';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/lobby/LobbyNavigationContainerRef';
|
||||
@@ -57,7 +58,9 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
safeAreaInsets = { [ 'left' ] }
|
||||
style = { contentWrapperStyles }>
|
||||
<BrandingImageBackground />
|
||||
<View style = { largeVideoContainerStyles }>
|
||||
<LargeVideo />
|
||||
</View>
|
||||
|
||||
@@ -15,18 +15,17 @@ export default {
|
||||
|
||||
buttonStylesBorderless: {
|
||||
iconStyle: {
|
||||
backgroundColor: 'transparent',
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 24
|
||||
},
|
||||
style: {
|
||||
backgroundColor: 'transparent',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
height: 24,
|
||||
width: 24
|
||||
}
|
||||
},
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
lobbyChatWrapper: {
|
||||
@@ -70,25 +69,27 @@ export default {
|
||||
},
|
||||
|
||||
largeVideoContainerWide: {
|
||||
position: 'absolute',
|
||||
marginRight: 'auto',
|
||||
height: '100%',
|
||||
marginRight: 'auto',
|
||||
position: 'absolute',
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
contentContainer: {
|
||||
alignSelf: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '50%'
|
||||
minHeight: '50%',
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
width: 400
|
||||
},
|
||||
|
||||
contentContainerWide: {
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: BaseTheme.spacing[6],
|
||||
marginVertical: BaseTheme.spacing[3],
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
left: '50%',
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
position: 'absolute',
|
||||
width: '50%'
|
||||
},
|
||||
@@ -124,7 +125,7 @@ export default {
|
||||
|
||||
formWrapper: {
|
||||
alignSelf: 'stretch',
|
||||
marginTop: 45
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
field: {
|
||||
@@ -134,7 +135,8 @@ export default {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
borderWidth: 2,
|
||||
height: BaseTheme.spacing[7],
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[3],
|
||||
marginHorizontal: 12,
|
||||
padding: BaseTheme.spacing[2],
|
||||
textAlign: 'center'
|
||||
},
|
||||
@@ -184,12 +186,12 @@ export default {
|
||||
|
||||
primaryButton: {
|
||||
backgroundColor: BaseTheme.palette.action01,
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
primaryButtonDisabled: {
|
||||
backgroundColor: BaseTheme.palette.action03Disabled,
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
primaryButtonText: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { openSheet } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconVolumeEmpty } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
@@ -32,7 +31,7 @@ class AudioDeviceToggleButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
this.props.dispatch(openDialog(AudioRoutePickerDialog));
|
||||
this.props.dispatch(openSheet(AudioRoutePickerDialog));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { NativeModules, Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
import { hideDialog, BottomSheet } from '../../../base/dialog';
|
||||
import { hideSheet, BottomSheet } from '../../../base/dialog';
|
||||
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
@@ -143,11 +141,6 @@ const deviceInfoMap = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The exported React {@code Component}.
|
||||
*/
|
||||
let AudioRoutePickerDialog_; // eslint-disable-line prefer-const
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} which prompts the user when a password
|
||||
* is required to join a conference.
|
||||
@@ -218,36 +211,10 @@ class AudioRoutePickerDialog extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
|
||||
// Trigger an initial update.
|
||||
AudioMode.updateDeviceList && AudioMode.updateDeviceList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a redux action to hide this sheet.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_hide() {
|
||||
this.props.dispatch(hideDialog(AudioRoutePickerDialog_));
|
||||
}
|
||||
|
||||
_onCancel: () => void;
|
||||
|
||||
/**
|
||||
* Cancels the dialog by hiding it.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
this._hide();
|
||||
}
|
||||
|
||||
_onSelectDeviceFn: (Device) => Function;
|
||||
|
||||
/**
|
||||
* Builds and returns a function which handles the selection of a device
|
||||
* on the sheet. The selected device will be used by {@code AudioMode}.
|
||||
@@ -258,7 +225,7 @@ class AudioRoutePickerDialog extends Component<Props, State> {
|
||||
*/
|
||||
_onSelectDeviceFn(device: Device) {
|
||||
return () => {
|
||||
this._hide();
|
||||
this.props.dispatch(hideSheet());
|
||||
AudioMode.setAudioDevice(device.uid || device.type);
|
||||
};
|
||||
}
|
||||
@@ -329,7 +296,7 @@ class AudioRoutePickerDialog extends Component<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<BottomSheet onCancel = { this._onCancel }>
|
||||
<BottomSheet>
|
||||
{ content }
|
||||
</BottomSheet>
|
||||
);
|
||||
@@ -348,6 +315,4 @@ function _mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
AudioRoutePickerDialog_ = translate(connect(_mapStateToProps)(AudioRoutePickerDialog));
|
||||
|
||||
export default AudioRoutePickerDialog_;
|
||||
export default translate(connect(_mapStateToProps)(AudioRoutePickerDialog));
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { getAppProp } from '../../base/app';
|
||||
import { readyToClose } from '../external-api/actions';
|
||||
|
||||
|
||||
/**
|
||||
* Sends a specific event to the native counterpart of the External API. Native
|
||||
@@ -24,3 +27,10 @@ export function sendEvent(store: Object, name: string, data: Object) {
|
||||
externalAPIScope
|
||||
&& NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounced sending of `readyToClose`.
|
||||
*/
|
||||
export const _sendReadyToClose = debounce(dispatch => {
|
||||
dispatch(readyToClose());
|
||||
}, 2500, { leading: true });
|
||||
|
||||
@@ -35,11 +35,12 @@ import {
|
||||
getLocalParticipant
|
||||
} from '../../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../../base/redux';
|
||||
import { toggleScreensharing } from '../../base/tracks';
|
||||
import { getLocalTracks, isLocalTrackMuted, toggleScreensharing } from '../../base/tracks';
|
||||
import { OPEN_CHAT, CLOSE_CHAT } from '../../chat';
|
||||
import { openChat } from '../../chat/actions';
|
||||
import { sendMessage, setPrivateMessageRecipient, closeChat } from '../../chat/actions.any';
|
||||
import { SET_PAGE_RELOAD_OVERLAY_CANCELED } from '../../overlay/actionTypes';
|
||||
import { setRequestingSubtitles } from '../../subtitles';
|
||||
import { muteLocal } from '../../video-menu/actions';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
|
||||
|
||||
@@ -372,6 +373,9 @@ function _registerForNativeEvents(store) {
|
||||
dispatch(sendMessage(message));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }) => {
|
||||
dispatch(setRequestingSubtitles(enabled));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,6 +394,7 @@ function _unregisterForNativeEvents() {
|
||||
eventEmitter.removeAllListeners(ExternalAPI.OPEN_CHAT);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.CLOSE_CHAT);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SEND_CHAT_MESSAGE);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,6 +534,11 @@ function _sendConferenceEvent(
|
||||
// transport an "equivalent".
|
||||
if (conference) {
|
||||
data.url = _normalizeUrl(conference[JITSI_CONFERENCE_URL_KEY]);
|
||||
|
||||
const localTracks = getLocalTracks(store.getState()['features/base/tracks']);
|
||||
const isAudioMuted = isLocalTrackMuted(localTracks, MEDIA_TYPE.AUDIO);
|
||||
|
||||
data.isAudioMuted = isAudioMuted;
|
||||
}
|
||||
|
||||
if (_swallowEvent(store, action, data)) {
|
||||
|
||||
@@ -4,20 +4,25 @@ import React, { useCallback } from 'react';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { DialInSummary } from '../../../invite';
|
||||
import Prejoin from '../../../prejoin/components/Prejoin.native';
|
||||
import { isWelcomePageEnabled } from '../../../welcome/functions';
|
||||
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
|
||||
import { rootNavigationRef } from '../rootNavigationContainerRef';
|
||||
import { screen } from '../routes';
|
||||
import {
|
||||
conferenceNavigationContainerScreenOptions,
|
||||
connectingScreenOptions,
|
||||
dialInSummaryScreenOptions,
|
||||
drawerNavigatorScreenOptions,
|
||||
navigationContainerTheme
|
||||
navigationContainerTheme,
|
||||
preJoinScreenOptions
|
||||
} from '../screenOptions';
|
||||
|
||||
import ConnectingPage from './ConnectingPage';
|
||||
import ConferenceNavigationContainer
|
||||
from './conference/components/ConferenceNavigationContainer';
|
||||
import WelcomePageNavigationContainer from './welcome/components/WelcomePageNavigationContainer';
|
||||
import { isWelcomePageAppEnabled } from './welcome/functions';
|
||||
import WelcomePageNavigationContainer
|
||||
from './welcome/components/WelcomePageNavigationContainer';
|
||||
|
||||
const RootStack = createNativeStackNavigator();
|
||||
|
||||
@@ -70,17 +75,15 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
|
||||
<RootStack.Screen
|
||||
component = { ConnectingPage }
|
||||
name = { screen.connecting }
|
||||
options = {{
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
}} />
|
||||
options = { connectingScreenOptions } />
|
||||
<RootStack.Screen
|
||||
component = { Prejoin }
|
||||
name = { screen.preJoin }
|
||||
options = { preJoinScreenOptions } />
|
||||
<RootStack.Screen
|
||||
component = { ConferenceNavigationContainer }
|
||||
name = { screen.conference.root }
|
||||
options = {{
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
}} />
|
||||
options = { conferenceNavigationContainerScreenOptions } />
|
||||
</RootStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
@@ -94,7 +97,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
|
||||
*/
|
||||
function mapStateToProps(state: Object) {
|
||||
return {
|
||||
isWelcomePageAvailable: isWelcomePageAppEnabled(state)
|
||||
isWelcomePageAvailable: isWelcomePageEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
gifsMenuOptions,
|
||||
inviteScreenOptions,
|
||||
liveStreamScreenOptions,
|
||||
lobbyNavigationContainerScreenOptions,
|
||||
navigationContainerTheme,
|
||||
participantsScreenOptions,
|
||||
recordingScreenOptions,
|
||||
@@ -101,15 +102,11 @@ const ConferenceNavigationContainer = () => {
|
||||
<ConferenceStack.Screen
|
||||
component = { StartRecordingDialog }
|
||||
name = { screen.conference.recording }
|
||||
options = {{
|
||||
...recordingScreenOptions
|
||||
}} />
|
||||
options = { recordingScreenOptions } />
|
||||
<ConferenceStack.Screen
|
||||
component = { StartLiveStreamDialog }
|
||||
name = { screen.conference.liveStream }
|
||||
options = {{
|
||||
...liveStreamScreenOptions
|
||||
}} />
|
||||
options = { liveStreamScreenOptions } />
|
||||
<ConferenceStack.Screen
|
||||
component = { SpeakerStats }
|
||||
name = { screen.conference.speakerStats }
|
||||
@@ -134,10 +131,7 @@ const ConferenceNavigationContainer = () => {
|
||||
<ConferenceStack.Screen
|
||||
component = { LobbyNavigationContainer }
|
||||
name = { screen.lobby.root }
|
||||
options = {{
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
}} />
|
||||
options = { lobbyNavigationContainerScreenOptions } />
|
||||
<ConferenceStack.Screen
|
||||
component = { AddPeopleDialog }
|
||||
name = { screen.conference.invite }
|
||||
@@ -158,7 +152,7 @@ const ConferenceNavigationContainer = () => {
|
||||
options = {{
|
||||
...carmodeScreenOptions,
|
||||
title: t('carmode.labels.title')
|
||||
}}/>
|
||||
}} />
|
||||
</ConferenceStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
@@ -2,25 +2,11 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { getFeatureFlag, WELCOME_PAGE_ENABLED } from '../../../../base/flags';
|
||||
import { IconArrowBack } from '../../../../base/icons';
|
||||
import HeaderNavigationButton
|
||||
from '../HeaderNavigationButton';
|
||||
import { navigationStyles } from '../styles';
|
||||
|
||||
/**
|
||||
* Determines whether the {@code WelcomePage} is enabled by the app itself
|
||||
* (e.g. Programmatically via the Jitsi Meet SDK for Android and iOS). Not to be
|
||||
* confused with {@link isWelcomePageUserEnabled}.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux state or {@link getState}
|
||||
* function.
|
||||
* @returns {boolean} If the {@code WelcomePage} is enabled by the app, then
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
export function isWelcomePageAppEnabled(stateful: Function | Object) {
|
||||
return Boolean(getFeatureFlag(stateful, WELCOME_PAGE_ENABLED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render header arrow back button for navigation.
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Platform } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import {
|
||||
getFeatureFlag,
|
||||
PREJOIN_PAGE_ENABLED
|
||||
} from '../../base/flags';
|
||||
import { IconClose } from '../../base/icons';
|
||||
import { toState } from '../../base/redux';
|
||||
import { cancelKnocking } from '../../lobby/actions.native';
|
||||
|
||||
import HeaderNavigationButton from './components/HeaderNavigationButton';
|
||||
|
||||
|
||||
/**
|
||||
* Close icon/text button based on platform.
|
||||
*
|
||||
@@ -29,3 +39,64 @@ export function screenHeaderCloseButton(goBack: Function) {
|
||||
src = { IconClose } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@code Prejoin page} is enabled by the app itself
|
||||
* (e.g. Programmatically via the Jitsi Meet SDK for Android and iOS).
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux state or {@link getState}
|
||||
* function.
|
||||
* @returns {boolean} If the {@code Prejoin} is enabled by the app, then
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
export function isPrejoinPageEnabled(stateful: Function | Object) {
|
||||
return getFeatureFlag(toState(stateful), PREJOIN_PAGE_ENABLED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close icon/text button for lobby screen based on platform.
|
||||
*
|
||||
* @returns {React.Component}
|
||||
*/
|
||||
export function lobbyScreenHeaderCloseButton() {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const goBack = useCallback(() => {
|
||||
dispatch(cancelKnocking());
|
||||
dispatch(appNavigate(undefined));
|
||||
}, [ dispatch ]);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
label = { t('dialog.close') }
|
||||
onPress = { goBack } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
onPress = { goBack }
|
||||
src = { IconClose } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we should auto-knock in case prejoin is enabled for the room.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux state or {@link getState}
|
||||
* function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldEnableAutoKnock(stateful: Function | Object) {
|
||||
const state = toState(stateful);
|
||||
const { displayName } = state['features/base/settings'];
|
||||
|
||||
if (isPrejoinPageEnabled(state)) {
|
||||
if (displayName) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import { CONFERENCE_FAILED, SET_ROOM } from '../../base/conference/actionTypes';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import { CONFERENCE_FAILED } from '../../base/conference/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { readyToClose } from '../external-api/actions';
|
||||
|
||||
|
||||
import { isWelcomePageAppEnabled } from './components/welcome/functions';
|
||||
import { navigateRoot } from './rootNavigationContainerRef';
|
||||
import { screen } from './routes';
|
||||
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_ROOM:
|
||||
return _setRoom(store, next, action);
|
||||
|
||||
case CONFERENCE_FAILED:
|
||||
return _conferenceFailed(store, next, action);
|
||||
@@ -23,47 +14,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Debounced sending of `readyToClose`.
|
||||
*/
|
||||
const _sendReadyToClose = debounce(dispatch => {
|
||||
dispatch(readyToClose());
|
||||
}, 2500, { leading: true });
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action
|
||||
* {@code SET_ROOM} is being dispatched within a specific
|
||||
* redux store.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code SET_ROOM}
|
||||
* which is being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setRoom({ dispatch, getState }, next, action) {
|
||||
const { room: oldRoom } = getState()['features/base/conference'];
|
||||
const result = next(action);
|
||||
const { room: newRoom } = getState()['features/base/conference'];
|
||||
const isWelcomePageEnabled = isWelcomePageAppEnabled(getState());
|
||||
|
||||
if (!oldRoom && newRoom) {
|
||||
navigateRoot(screen.conference.root);
|
||||
} else if (!newRoom) {
|
||||
if (isWelcomePageEnabled) {
|
||||
navigateRoot(screen.root);
|
||||
} else {
|
||||
// For JitsiSDK, WelcomePage is not available
|
||||
_sendReadyToClose(dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to handle the conference failed event and navigate the user to the lobby screen
|
||||
* based on the failure reason.
|
||||
@@ -73,20 +23,13 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||
* @param {Object} action - The Redux action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||
const state = getState();
|
||||
const isWelcomePageEnabled = isWelcomePageAppEnabled(state);
|
||||
function _conferenceFailed({ dispatch }, next, action) {
|
||||
const { error } = action;
|
||||
|
||||
// We need to cover the case where knocking participant
|
||||
// is rejected from entering the conference
|
||||
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||
if (isWelcomePageEnabled) {
|
||||
navigateRoot(screen.root);
|
||||
} else {
|
||||
// For JitsiSDK, WelcomePage is not available
|
||||
_sendReadyToClose(dispatch);
|
||||
}
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// $FlowExpectedError
|
||||
import { toState } from '../../base/redux';
|
||||
import { isWelcomePageEnabled } from '../../welcome/functions';
|
||||
import { _sendReadyToClose } from '../external-api/functions';
|
||||
|
||||
import { screen } from './routes';
|
||||
|
||||
export const rootNavigationRef = React.createRef();
|
||||
|
||||
|
||||
/**
|
||||
* User defined navigation action included inside the reference to the container.
|
||||
*
|
||||
@@ -13,7 +17,32 @@ export const rootNavigationRef = React.createRef();
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function navigateRoot(name: string, params: Object) {
|
||||
// $FlowExpectedError
|
||||
return rootNavigationRef.current?.navigate(name, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* User defined navigation action included inside the reference to the container.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function goBack() {
|
||||
return rootNavigationRef.current?.goBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to Welcome page, if it's available.
|
||||
*
|
||||
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
|
||||
* @param {Function} dispatch - Redux dispatch function.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function goBackToRoot(stateful: Function | Object, dispatch: Function) {
|
||||
const state = toState(stateful);
|
||||
|
||||
if (isWelcomePageEnabled(state)) {
|
||||
navigateRoot(screen.root);
|
||||
} else {
|
||||
// For JitsiSDK, WelcomePage is not available
|
||||
_sendReadyToClose(dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const screen = {
|
||||
},
|
||||
dialInSummary: 'Dial-In Info',
|
||||
connecting: 'Connecting',
|
||||
preJoin: 'Pre-Join',
|
||||
conference: {
|
||||
root: 'Conference root',
|
||||
main: 'Conference',
|
||||
|
||||
@@ -12,7 +12,8 @@ import BaseTheme from '../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import { goBack } from './components/conference/ConferenceNavigationContainerRef';
|
||||
import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
|
||||
import { screenHeaderCloseButton } from './functions';
|
||||
import { lobbyScreenHeaderCloseButton, screenHeaderCloseButton } from './functions';
|
||||
import { goBack as goBackToWelcomeScreen } from './rootNavigationContainerRef';
|
||||
|
||||
|
||||
/**
|
||||
@@ -81,8 +82,9 @@ export const welcomeScreenOptions = {
|
||||
headerStyle: {
|
||||
backgroundColor: BaseTheme.palette.screen01Header
|
||||
},
|
||||
// eslint-disable-next-line no-empty-function
|
||||
headerTitle: () => {}
|
||||
headerTitleStyle: {
|
||||
color: BaseTheme.palette.text01
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -164,7 +166,6 @@ export const chatTabBarOptions = {
|
||||
* Screen options for presentation type modals.
|
||||
*/
|
||||
export const presentationScreenOptions = {
|
||||
animation: 'slide_from_right',
|
||||
headerBackTitleVisible: false,
|
||||
headerLeft: () => screenHeaderCloseButton(goBack),
|
||||
headerStatusBarHeight: 0,
|
||||
@@ -183,7 +184,10 @@ export const presentationScreenOptions = {
|
||||
/**
|
||||
* Screen options for car mode.
|
||||
*/
|
||||
export const carmodeScreenOptions = presentationScreenOptions;
|
||||
export const carmodeScreenOptions = {
|
||||
...presentationScreenOptions,
|
||||
orientation: 'portrait'
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for chat.
|
||||
@@ -195,7 +199,8 @@ export const chatScreenOptions = presentationScreenOptions;
|
||||
*/
|
||||
export const dialInSummaryScreenOptions = {
|
||||
...presentationScreenOptions,
|
||||
headerLeft: undefined
|
||||
animation: 'slide_from_bottom',
|
||||
headerLeft: () => screenHeaderCloseButton(goBackToWelcomeScreen)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -231,7 +236,10 @@ export const liveStreamScreenOptions = presentationScreenOptions;
|
||||
/**
|
||||
* Screen options for lobby modal.
|
||||
*/
|
||||
export const lobbyScreenOptions = presentationScreenOptions;
|
||||
export const lobbyScreenOptions = {
|
||||
...presentationScreenOptions,
|
||||
headerLeft: () => lobbyScreenHeaderCloseButton()
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for lobby chat modal.
|
||||
@@ -269,3 +277,38 @@ export const sharedDocumentScreenOptions = {
|
||||
android: 'all'
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for connecting screen.
|
||||
*/
|
||||
export const connectingScreenOptions = {
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for pre-join screen.
|
||||
*/
|
||||
export const preJoinScreenOptions = {
|
||||
gestureEnabled: false,
|
||||
headerStyle: {
|
||||
backgroundColor: BaseTheme.palette.screen02Header
|
||||
},
|
||||
headerTitle: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for conference navigation container screen.
|
||||
*/
|
||||
export const conferenceNavigationContainerScreenOptions = {
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for lobby navigation container screen.
|
||||
*/
|
||||
export const lobbyNavigationContainerScreenOptions = {
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
};
|
||||
|
||||
@@ -5,13 +5,13 @@ import { NativeModules } from 'react-native';
|
||||
/**
|
||||
* Enabled/Disables the PictureInPicture mode in PiP native module.
|
||||
*
|
||||
* @param {boolean} disabled - Whether the PiP mode should be disabled.
|
||||
* @param {boolean} enabled - Whether the PiP mode should be enabled.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setPictureInPictureDisabled(disabled: boolean) {
|
||||
export function setPictureInPictureEnabled(enabled: boolean) {
|
||||
const { PictureInPicture } = NativeModules;
|
||||
|
||||
if (PictureInPicture) {
|
||||
PictureInPicture.setPictureInPictureDisabled(disabled);
|
||||
PictureInPicture.setPictureInPictureEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { openSheet } from '../base/dialog';
|
||||
import { SharedVideoMenu } from '../video-menu';
|
||||
import { LocalVideoMenu } from '../video-menu/components/native';
|
||||
import ConnectionStatusComponent
|
||||
@@ -11,6 +11,7 @@ import { SET_VOLUME } from './actionTypes';
|
||||
import {
|
||||
ContextMenuLobbyParticipantReject
|
||||
} from './components/native';
|
||||
import RoomParticipantMenu from './components/native/RoomParticipantMenu';
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
@@ -20,7 +21,7 @@ export * from './actions.any';
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showContextMenuReject(participant: Object) {
|
||||
return openDialog(ContextMenuLobbyParticipantReject, { participant });
|
||||
return openSheet(ContextMenuLobbyParticipantReject, { participant });
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +32,7 @@ export function showContextMenuReject(participant: Object) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showConnectionStatus(participantID: string) {
|
||||
return openDialog(ConnectionStatusComponent, { participantID });
|
||||
return openSheet(ConnectionStatusComponent, { participantID });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,9 +47,9 @@ export function showContextMenuDetails(participantId: string, local: boolean = f
|
||||
const { remoteVideoMenu } = getState()['features/base/config'];
|
||||
|
||||
if (local) {
|
||||
dispatch(openDialog(LocalVideoMenu));
|
||||
dispatch(openSheet(LocalVideoMenu));
|
||||
} else if (!remoteVideoMenu?.disabled) {
|
||||
dispatch(openDialog(RemoteVideoMenu, { participantId }));
|
||||
dispatch(openSheet(RemoteVideoMenu, { participantId }));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -60,7 +61,7 @@ export function showContextMenuDetails(participantId: string, local: boolean = f
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showSharedVideoMenu(participantId: string) {
|
||||
return openDialog(SharedVideoMenu, { participantId });
|
||||
return openSheet(SharedVideoMenu, { participantId });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,3 +82,17 @@ export function setVolume(participantId: string, volume: number) {
|
||||
volume
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the breakout room participant menu.
|
||||
*
|
||||
* @param {Object} room - The room the participant is in.
|
||||
* @param {string} participantJid - The jid of the participant.
|
||||
* @param {string} participantName - The display name of the participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showRoomParticipantMenu(room: Object, participantJid: string, participantName: string) {
|
||||
return openSheet(RoomParticipantMenu, { room,
|
||||
participantJid,
|
||||
participantName });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
@@ -7,7 +5,7 @@ import { Text } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../../../analytics';
|
||||
import { hideDialog } from '../../../../../base/dialog/actions';
|
||||
import { hideSheet } from '../../../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../../../base/dialog/components/native/BottomSheet';
|
||||
import {
|
||||
Icon,
|
||||
@@ -29,7 +27,6 @@ type Props = {
|
||||
|
||||
const BreakoutRoomContextMenu = ({ room }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const closeDialog = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const { hideJoinRoomButton } = useSelector(getBreakoutRoomsConfig);
|
||||
const { t } = useTranslation();
|
||||
@@ -37,23 +34,22 @@ const BreakoutRoomContextMenu = ({ room }: Props) => {
|
||||
const onJoinRoom = useCallback(() => {
|
||||
sendAnalytics(createBreakoutRoomsEvent('join'));
|
||||
dispatch(moveToRoom(room.jid));
|
||||
closeDialog();
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRemoveBreakoutRoom = useCallback(() => {
|
||||
dispatch(removeBreakoutRoom(room.jid));
|
||||
closeDialog();
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onCloseBreakoutRoom = useCallback(() => {
|
||||
dispatch(closeBreakoutRoom(room.id));
|
||||
closeDialog();
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
addScrollViewPadding = { false }
|
||||
onCancel = { closeDialog }
|
||||
showSlidingView = { true }>
|
||||
{
|
||||
!hideJoinRoomButton && (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user