Compare commits

...

34 Commits

Author SHA1 Message Date
tmoldovan8x8
8b6a1e4451 fix(rn, pip) enables PiP on conference mounted 2022-06-23 16:40:55 +02:00
tmoldovan8x8
4d6ca4383f fix(android) calls startForeground in onCreate
Call startForeground in onCreate to avoid android.app.RemoteServiceException thrown by the system.
2022-06-23 09:41:32 +02:00
Robert Pintilii
ddce2e6bec feat(breakout-rooms) add context menu to participants in other rooms 2022-06-23 09:40:11 +02:00
Robert Pintilii
7dca91a50a fix(local-recording) Add notification config and style fixes (#11728)
Add analytics
2022-06-22 12:52:22 +03:00
Saúl Ibarra Corretgé
31348179d4 fix(auth) simplify auth-and-upgrade procedure
It's not necessary to perform a full join, sending a conference IQ is
enough.
2022-06-21 19:20:09 +02:00
Calinteodor
e77679d025 feat(dynamic-branding): SVG branding image needs to cover the entire screen (#11724)
* feat(dynamic-branding) scale SVG branding image to cover entire screen
2022-06-21 17:51:25 +03:00
Titus Moldovan
44a9363f5b feat(mobile, external_api) exposes setClosedCaptionsEnabled 2022-06-21 16:18:31 +02:00
Calinteodor
bb76090bce feat(lobby/prejoin): updates
* feat(base/modal) added keyboard dismiss functionality

* feat(lobby) updated ui and start knocking if name is set

* feat(prejoin) updated ui and hide input if name is not required

* feat(prejoin) updated join button styles

* feat(prejoin) removed extra empty space

* feat(prejoin) updated disable join condition

* feat(base/modal) moved keaboard dismiss functionality

* feat(conference) updated auto knock condition

* feat(prejoin) updated button styles and disabling condition

* feat(lobby) updated styles

* feat(lobby/prejoin) updated styles for buttons and inputs

* feat(lobby/prejoin) updated contentContainer styles

* feat(lobby/prejoin) created shouldEnableAutoKnock helper
2022-06-21 16:16:38 +02:00
Robert Pintilii
d0790736db feat(external-api) Add participants pane toggled event (#11718) 2022-06-21 16:23:33 +03:00
Jaya Allamsetty
0308ba71b1 fix(audio-only) Do not unmute camera when SS is in progress.
If the audio-only mode is automatically disabled when user starts a screenshare while in audio-only mode, do not unmute the camera track.
2022-06-21 07:48:02 -04:00
Jaya Allamsetty
f7d1a5ec80 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1455.0.0+f3a2f61e...v1457.0.0+ad75454f
2022-06-21 07:24:38 -04:00
Saúl Ibarra Corretgé
d61fe58fcf fix(rn,styles) cleanup unused styles 2022-06-21 11:48:07 +02:00
Titus Moldovan
b428ce2dcd fix(pip) make PiP disabled by default
This reverts commit c84c3c61e2c24014b43023316627f7747bbca7a6.
2022-06-21 10:36:21 +03:00
Titus Moldovan
d1c9720033 fix(rn) add backhandler on Prejoin screen 2022-06-21 10:36:21 +03:00
Andrei Gavrilescu
c29e8bbdd1 feat(analytics): obfuscate room name (#11587)
* obfuscate room name

* fixed js-sha version

* add comma

* check for room change
2022-06-21 09:53:07 +03:00
Horatiu Muresan
3fb3be9727 feat(invite) Consider SHARING_FEATURES on the email invites (#11711) 2022-06-20 23:18:06 +03:00
Saúl Ibarra Corretgé
517ec29d85 fix(rn,dialogs) fix displaying dialogs on top of modal screens
Render them together with bottom sheets in a FullWindowOverlay.
2022-06-20 18:37:28 +03:00
Saúl Ibarra Corretgé
6ad279f029 fix(rn, bottomsheet) fix not rendering above presentation sheets
Move all sheets to render in a new container which uses FullWindowOverlay, which allows rendering above presentation controllers on iOS.
2022-06-20 16:53:19 +02:00
José Luís Andrade
0e98f90205 fix(lang) update Portuguese translation 2022-06-18 23:26:06 +02:00
Saúl Ibarra Corretgé
2c5b132483 fix(util) fix parsing strings in parseURLParams
After https://github.com/jitsi/jitsi-meet/pull/11607 we might call it
with a string. Be nice and accept that in addition to URL objects.
2022-06-18 23:17:55 +02:00
Calinteodor
4d8f29d4fe feat(rn,dynamic-brandind) added background image url to prejoin and lobby 2022-06-18 21:59:10 +02:00
Ali Alhaidary
22be96d838 fix(lang) update Arabic translation 2022-06-18 20:54:51 +02:00
Дамян Минков
ccc1157df5 fix: Fixes navigating back to welcome page after clicking cancel on login window.
It was handling just conference_failed with password required, but not connection failed with password required.
2022-06-17 15:19:06 +03:00
Дамян Минков
f613126776 fix: Hides pre join screen in few login window cases.
In Firefox pre-join was grabbing the focus and yuo cannot type username and password after clicking I'm the host button.
2022-06-17 15:19:06 +03:00
Robert Pintilii
38b21e986d fix(pinning) Fix pinning (#11693)
Hide Pin to Stage button while screensharing
Fix pin indicator while screensharing
2022-06-17 15:15:14 +03:00
Horatiu Muresan
38abca8a65 fix(carmode) Force potrait mode, add connection indicator 2022-06-17 12:54:51 +02:00
Дамян Минков
f3c6b54ffa fix: When adding a room param to urls check for previous params. (#11607)
* fix: When adding a room param to urls check for previous params.

* squash: Uses URL object to modify the url.

* squash: Use common connection options from base/connection.

Normalizes bosh url and for web.

* squash: Adds release param to external api and handles it.

* feat: Adds release handling for mobile(links in welcome page).

* squash: Fixes comments.
2022-06-16 15:27:41 +03:00
Gabriel Borlea
7dd85bb6ad fix(face-landmarks): work only when one face is detected (#11661)
* fix(face-landmarks): work only when one face is detected

* fix: remove redundant check for detection

* fix(face-landmarks): re-center and stop when more faces detected

* fix: remove faceCount checking when sending message from worker

* fix: add again the faceCount

* fix: add comment

* code review
2022-06-16 14:50:31 +03:00
Gabriel Borlea
624f88e069 add(face-landmarks): flag for rtc stats (#11682)
* add(face-landmarks): flag for rtc stats

* fix: check is faceLandmarks is defined
2022-06-16 14:13:36 +03:00
Calinteodor
fbf693b2dc feat(mobile/navigation) updated screens that have footer
* feat(mobile/navigation) updated screens that have footer

* feat(chat/native) reverted style change

* feat(chat/native) reverted changes and added input vertical padding

* feat(base/modal) replaced headerHeight with top safe area inset

* feat(carmode/native) removed unused import and fixed linter

* feat(chat/polls/native) reverted style changes

* feat(base/modal) added isModalPresentation default prop

* feat(base/modal) made isModalPresentation optional

* feat(base/modal) headerHeight based on top notch devices

* feat(polls) updated styles

* feat(base/modal) updated comment
2022-06-16 11:49:53 +02:00
Calinteodor
dbf7bf4750 feat(prejoin) native prejoin screen and other navigation updates
* feat(prejoin) created native Prejoin screen

* feat(prejoin) fixed useState callback and updates warnings

* feat(prejoin) created styles file

* feat(prejoin) moved nav from middleware to appNavigate, created native DeviceStatus

* feat(prejoin) updated styles

* feat(prejoin) review remarks pt. 1

* feat(prejoin) removed unused styles

* feat(prejoin) review remarks pt. 2

* feat(prejoin) comment fix

* feat(prejoin) added header title

* feat(prejoin) review remarks

* feat(lobby) updated styles

* feat(prejoin) updated lobby screen header button functionality

* feat(prejoin) review remarks pt 3

* feat(welcome) removed VideoSwitch component

* feat(mobile/navigation) fixed linter

* feat(welcome) moved isWelcomePageEnabled to functions.ts

* feat(mobile/navigation) screen options and order updates

* feat(app) review remark

* feat(welcome) added translation for screen header title and fixed build

* feat(mobile/navigation) added screen title translation and created screen option

* feat(mobile/navigation) fixed screenOptions import

* feat(mobile/navigation) added DialInSummary title translation, fixed animation and close button

* feat(welcome) fixed build

* feat(welcome) removed extra check

* feat(prejoin) review remarks pt 4

* feat(prejoin) added Join in low bandwidth mode btn

* feat(welcome) changed welcome screen header title

* fixup lobby close
2022-06-16 11:49:07 +02:00
Titus Moldovan
d31eb3b248 fix(android) parse initial isAudioMuted when starting JitsiMeetOngoingConferenceService 2022-06-16 11:54:25 +03:00
Titus Moldovan
9b75fc98c1 feat(rn) send isAudioMuted on conferenceEvent 2022-06-16 11:54:25 +03:00
hmuresan
1ee9f6a7e5 feat(extension-banner) Show edge extension when edge browser detected 2022-06-16 11:43:03 +03:00
142 changed files with 2121 additions and 1550 deletions

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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");
}
}
}

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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'
// }
// ]
// },

View File

@@ -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 })

View File

@@ -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 {

View File

@@ -749,4 +749,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: bef1335067eaa4e8c558b1248f8ab3948de855bc
COCOAPODS: 1.11.2
COCOAPODS: 1.11.3

View File

@@ -27,5 +27,6 @@
- (void)closeChat;
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
- (void)sendSetVideoMuted:(BOOL)muted;
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled;
@end

View File

@@ -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

View File

@@ -45,5 +45,6 @@
- (void)closeChat;
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
- (void)setVideoMuted:(BOOL)muted;
- (void)setClosedCaptionsEnabled:(BOOL)enabled;
@end

View File

@@ -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
/**

View File

@@ -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": "الملف الشخصي",

View File

@@ -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"
},

View File

@@ -116,6 +116,7 @@
},
"chromeExtensionBanner": {
"buttonText": "Install Chrome Extension",
"buttonTextEdge": "Install Edge Extension",
"close": "Close",
"dontShowAgain": "Dont 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",

View File

@@ -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.
*

View File

@@ -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({

View File

@@ -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
View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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,
},

View File

@@ -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);

View File

@@ -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);
}
};
}

View File

@@ -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;

View File

@@ -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>
);
}
}

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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

View File

@@ -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
};
}

View File

@@ -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());
}
}

View File

@@ -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')
}
});

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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)',

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -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).

View File

@@ -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);
});
}

View File

@@ -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);

View File

@@ -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',

View 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;
}

View File

@@ -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.
*

View File

@@ -16,6 +16,8 @@ export {
} from './actions.native';
import logger from './logger';
export * from './actions.any';
/**
* Opens new connection.
*

View File

@@ -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';

View File

@@ -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

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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>
);
}
}

View File

@@ -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>
);
}

View File

@@ -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'
}
});

View File

@@ -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;

View File

@@ -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.

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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;
}

View File

@@ -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('&')) || [];

View File

@@ -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();
}

View File

@@ -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: {

View File

@@ -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>

View File

@@ -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)
};

View File

@@ -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
};
}

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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.

View File

@@ -203,5 +203,9 @@ export default {
color: BaseTheme.palette.text01,
marginBottom: 32,
...BaseTheme.typography.bodyShortRegularLarge
},
connectionIndicatorIcon: {
fontSize: 20
}
};

View File

@@ -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')
}
});

View File

@@ -32,6 +32,11 @@ export type Props = {
*/
participantId: string,
/**
* Custom icon style.
*/
iconStyle?: Object,
/**
* The source name of the track.
*/

View File

@@ -58,7 +58,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
}}>
<BaseIndicator
icon = { IconConnectionActive }
iconStyle = { iconStyle } />
iconStyle = { this.props.iconStyle || iconStyle } />
</View>
);
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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
};
}

View File

@@ -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)
}
}

View File

@@ -140,8 +140,7 @@ export function loadWorker() {
worker.postMessage({
type: INIT_WORKER,
baseUrl,
detectionTypes,
maxFacesDetected: faceLandmarks?.maxFacesDetected
detectionTypes
});
dispatch(startFaceLandmarksDetection());

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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>

View File

@@ -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')
});
}

View File

@@ -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.
*

View File

@@ -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>

View File

@@ -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: {

View File

@@ -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));
}
}

View File

@@ -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));

View File

@@ -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 });

View File

@@ -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)) {

View File

@@ -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)
};
}

View File

@@ -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>
);

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -13,6 +13,7 @@ export const screen = {
},
dialInSummary: 'Dial-In Info',
connecting: 'Connecting',
preJoin: 'Pre-Join',
conference: {
root: 'Conference root',
main: 'Conference',

View File

@@ -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
};

View File

@@ -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);
}
}

View File

@@ -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 });
}

View File

@@ -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