Compare commits

...

15 Commits

Author SHA1 Message Date
Calin-Teodor
32e018f43b chore(rnsdk, version): bump to 1.0.0 2023-11-17 13:00:58 +02:00
Abbas Al-Mansoori
e3dd39f6e4 refactor(rnsdk): remove redundant audio and video actions (#14066)
* refactor(rnsdk): remove redundant audio and video actions
2023-11-17 12:27:56 +02:00
Philipp Fruck
92c43c03ad fix(native app): Display poll creator name
In the jitsi web app, the poll creator is displayed
for all published votes whereas in the native app
the current username of the participant has been
displayed for all polls regardless of the creator
2023-11-16 12:57:28 +02:00
Abbas Al-Mansoori
8d8bd4d8d4 feat(rnsdk): add audio and video muted state changed 2023-11-16 12:57:00 +02:00
Calin-Teodor
8dda34a502 feat(authentication): group config options inside an object param 2023-11-15 16:33:39 +02:00
Calin-Teodor
3a85a915bf feat(authentication): used config for control over joining audio/video mute/unmute 2023-11-15 12:43:54 +02:00
Calinteodor
17b0a12224 ref(authentication): handle joining in low bandwidth mode (#14032)
ref(authentication): handle joining in low bandwidth mode
2023-11-14 12:16:53 +02:00
Calin-Teodor
b52394d328 feat(base/ui): removed unneeded lineHeight value 2023-11-09 12:12:10 +02:00
Calinteodor
cadb216cea feat(base/modal): changed hasTabNavigator to hasExtraHeaderHeight (#14033)
* feat(base/modal): changed hasTabNavigator to hasExtraHeaderHeight
2023-11-08 12:01:09 +02:00
Calinteodor
2bf8e6cfce feat(filmstrip): fixed indicators container dissapear when not in tile view (#14031)
* feat(filmstrip): fixed indicators container dissapear when not in tile view
2023-11-08 10:42:00 +02:00
Calin-Teodor
261a8a6740 feat(participants-pane): fixed visitors label position 2023-11-07 18:40:09 +02:00
Calin-Teodor
35dbcd9b9d feat(filmstrip): fixed indicators container ui 2023-11-07 18:39:46 +02:00
Calinteodor
45bf00d096 sdk(react-native-sdk): rnsdk screenshare android fix (#13884)
sdk(react-native-sdk): rnsdk screenshare android fix
2023-11-07 12:23:17 +02:00
Calinteodor
78847148d8 feat(recent-list): fix undefined error that breaks visitor joining (#14024)
* feat(recent-list): fix undefined error that breaks visitor joining

* feat(recent-list): revert variable name change

* feat(recent-list): fixed linter
2023-11-06 23:13:10 +02:00
Calin-Teodor
c53e7ff9cd chore(rn, versions): bump app and sdk versions 2023-10-30 15:46:50 +01:00
38 changed files with 669 additions and 123 deletions

4
.gitignore vendored
View File

@@ -99,6 +99,10 @@ tsconfig.json
#
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=99.0.0
sdkVersion=99.0.0
appVersion=23.6.0
sdkVersion=8.6.0

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>23.6.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>23.6.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>23.6.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>23.6.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>8.6.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<string>8.6.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -73,6 +73,13 @@ cd ios && pod install && cd ..
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
```
- In `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
```xml
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaProjection" />
```
This will take care of the screen share feature.
If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native

View File

@@ -0,0 +1,44 @@
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.content.Context;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule
extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
Context activityContext = context.getCurrentActivity();
JitsiMeetOngoingConferenceService.launch(context, activityContext);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetOngoingConferenceService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 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.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetOngoingConferenceService extends Service {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
public static void launch(Context context, Context activityContext) {
RNOngoingNotification.createOngoingConferenceNotificationChannel(activityContext);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(RNOngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -21,6 +21,7 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),

View File

@@ -0,0 +1,97 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createOngoingConferenceNotificationChannel(Context activityContext) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (activityContext == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) activityContext.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel("JitsiOngoingConferenceChannel");
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel("JitsiOngoingConferenceChannel", activityContext.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "JitsiOngoingConferenceChannel");
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
return builder.build();
}
}

View File

@@ -19,6 +19,8 @@ import { setAudioMuted, setVideoMuted } from './react/features/base/media/action
interface IEventListeners {
onAudioMutedChanged?: Function;
onVideoMutedChanged?: Function;
onConferenceBlurred?: Function;
onConferenceFocused?: Function;
onConferenceJoined?: Function;
@@ -107,6 +109,8 @@ export const JitsiMeeting = forwardRef((props: IAppProps, ref) => {
setAppProps({
'flags': flags,
'rnSdkHandlers': {
onAudioMutedChanged: eventListeners?.onAudioMutedChanged,
onVideoMutedChanged: eventListeners?.onVideoMutedChanged,
onConferenceBlurred: eventListeners?.onConferenceBlurred,
onConferenceFocused: eventListeners?.onConferenceFocused,
onConferenceJoined: eventListeners?.onConferenceJoined,

View File

@@ -1,16 +1,16 @@
{
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"version": "1.0.0",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
@@ -19,7 +19,7 @@
"@react-navigation/native": "6.1.7",
"@react-navigation/stack": "6.3.17",
"@xmldom/xmldom": "0.8.7",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
@@ -27,7 +27,7 @@
"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/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -69,7 +69,7 @@
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
@@ -80,7 +80,7 @@
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.3",
"react-native-webrtc": "111.0.6",
"react-native-webview": "13.5.1"
}
},
@@ -396,13 +396,13 @@
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"node_modules/@jitsi/js-utils": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"node_modules/@jitsi/js-utils/node_modules/js-md5": {
@@ -993,9 +993,23 @@
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/binary-extensions": {
"version": "2.2.0",
@@ -1010,11 +1024,6 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/bowser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz",
"integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2481,14 +2490,14 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"integrity": "sha512-ky3q5gLeLDHFtenXe7+QlxAh33opz0a3bqEp8svA9urR1AICUi0QthOwmUerTdfSRFU3wfVObF9axlX1fbHZ3A==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"integrity": "sha512-+6nI2ExiTS8aKnq3ZDpYKs1MSDtUHaK4hIhVxyI7Db6ZojrMOpZx6lnrmqz+AHWllSHvIggFz48du7tG6UYANw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/rtcstats": "9.6.0",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -2509,6 +2518,30 @@
"webrtc-adapter": "8.1.1"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/rtcstats": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.6.0.tgz",
"integrity": "sha512-eFFD6vp1dFwvjAR+NRjsxlvclFvqo/nbU2RcSLzXbIjG/WraMHyxMSzllZyT1T3S2iPehS4dnZ/ZYyz5WYHTaA==",
"dependencies": {
"@jitsi/js-utils": "^2.2.0",
"@jitsi/logger": "2.0.2",
"sdp": "^3.0.3",
"uuid": "^8.3.2"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/rtcstats/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/lib-jitsi-meet/node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"node_modules/lib-jitsi-meet/node_modules/uuid": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
@@ -3570,6 +3603,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@@ -4044,13 +4095,13 @@
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"@jitsi/js-utils": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"requires": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
},
"dependencies": {
"js-md5": {
@@ -4445,9 +4496,9 @@
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"binary-extensions": {
"version": "2.2.0",
@@ -4459,11 +4510,6 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"bowser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz",
"integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5476,12 +5522,12 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"integrity": "sha512-ky3q5gLeLDHFtenXe7+QlxAh33opz0a3bqEp8svA9urR1AICUi0QthOwmUerTdfSRFU3wfVObF9axlX1fbHZ3A==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"integrity": "sha512-+6nI2ExiTS8aKnq3ZDpYKs1MSDtUHaK4hIhVxyI7Db6ZojrMOpZx6lnrmqz+AHWllSHvIggFz48du7tG6UYANw==",
"requires": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/rtcstats": "9.6.0",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -5502,6 +5548,29 @@
"webrtc-adapter": "8.1.1"
},
"dependencies": {
"@jitsi/rtcstats": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.6.0.tgz",
"integrity": "sha512-eFFD6vp1dFwvjAR+NRjsxlvclFvqo/nbU2RcSLzXbIjG/WraMHyxMSzllZyT1T3S2iPehS4dnZ/ZYyz5WYHTaA==",
"requires": {
"@jitsi/js-utils": "^2.2.0",
"@jitsi/logger": "2.0.2",
"sdp": "^3.0.3",
"uuid": "^8.3.2"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"uuid": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
@@ -6263,6 +6332,11 @@
"is-typed-array": "^1.1.9"
}
},
"ua-parser-js": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA=="
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"version": "1.0.0",
"description": "React Native SDK for Jitsi Meet.",
"main": "index.tsx",
"license": "Apache-2.0",
@@ -11,7 +11,7 @@
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
@@ -20,7 +20,7 @@
"@react-navigation/native": "6.1.7",
"@react-navigation/stack": "6.3.17",
"@xmldom/xmldom": "0.8.7",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
@@ -28,7 +28,7 @@
"i18next-http-backend": "^2.2.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -73,7 +73,7 @@
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"react-native-safe-area-context": "4.7.1",
"react-native-screens": "3.24.0",
"react-native-sound": "0.11.2",
@@ -81,7 +81,7 @@
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.3",
"react-native-webrtc": "111.0.6",
"react-native-webview": "13.5.1"
},
"overrides": {
@@ -97,4 +97,4 @@
"keywords": [
"react-native"
]
}
}

View File

@@ -6,7 +6,9 @@ const packageJSON = require('../package.json');
const SDKPackageJSON = require('./package.json');
const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
const androidMainSourcePath = '../android/sdk/src/main/res';
const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
const androidMainTargetPath = './android/src/main/res';
const iosSrcPath = '../ios/sdk/src';
const iosDestPath = './ios/src';
@@ -169,6 +171,30 @@ copyFolderRecursiveSync(
`${androidSourcePath}/log`,
`${androidTargetPath}/log`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/values`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-hdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-mdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidSourcePath}/net`,
`${androidTargetPath}/log`

View File

@@ -5,12 +5,15 @@ import { openTokenAuthUrl } from '../authentication/actions';
// @ts-ignore
import { getTokenAuthUrl, isTokenAuthEnabled } from '../authentication/functions';
import { getJwtExpirationDate } from '../base/jwt/functions';
import { MEDIA_TYPE } from '../base/media/constants';
import { isLocalTrackMuted } from '../base/tracks/functions.any';
import { getLocationContextRoot, parseURIString } from '../base/util/uri';
import { addTrackStateToURL } from './functions.any';
import logger from './logger';
import { IStore } from './types';
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
@@ -104,7 +107,11 @@ export function maybeRedirectToTokenAuthUrl(
dispatch: IStore['dispatch'], getState: IStore['getState'], failureCallback: Function) {
const state = getState();
const config = state['features/base/config'];
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const { startAudioOnly } = config;
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
if (!isTokenAuthEnabled(config)) {
return false;
@@ -120,7 +127,18 @@ export function maybeRedirectToTokenAuthUrl(
const room = state['features/base/conference'].room;
const { tenant } = parseURIString(locationURL.href) || {};
getTokenAuthUrl(config, room, tenant, true, locationURL)
getTokenAuthUrl(
config,
locationURL,
{
audioMuted,
audioOnlyEnabled: audioOnlyEnabled || startAudioOnly,
skipPrejoin: true,
videoMuted
},
room,
tenant
)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');

View File

@@ -55,8 +55,20 @@ function _getWebConferenceRoute(state: IReduxState) {
&& !state['features/base/jwt'].jwt && room) {
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const { startAudioOnly } = config;
return getTokenAuthUrl(config, room, tenant, false, locationURL)
return getTokenAuthUrl(
config,
locationURL,
{
audioMuted: false,
audioOnlyEnabled: startAudioOnly,
skipPrejoin: false,
videoMuted: false
},
room,
tenant
)
.then((url: string | undefined) => {
route.href = url;

View File

@@ -13,29 +13,65 @@ export const isTokenAuthEnabled = (config: IConfig): boolean =>
/**
* Returns the state that we can add as a parameter to the tokenAuthUrl.
*
* @param {URL} locationURL - The location URL.
* @param {Object} options: - Config options {
* audioMuted: boolean | undefined
* audioOnlyEnabled: boolean | undefined,
* skipPrejoin: boolean | undefined,
* videoMuted: boolean | undefined
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {boolean} skipPrejoin - Whether to skip pre-join page.
* @param {URL} locationURL - The location URL.
*
* @returns {Object} The state object.
*/
export const _getTokenAuthState = (
locationURL: URL,
options: {
audioMuted: boolean | undefined;
audioOnlyEnabled: boolean | undefined;
skipPrejoin: boolean | undefined;
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
locationURL: URL): object => {
tenant: string | undefined): object => {
const state = {
room: roomName,
roomSafe: getBackendSafeRoomName(roomName),
tenant
};
const {
audioMuted = false,
audioOnlyEnabled = false,
skipPrejoin = false,
videoMuted = false
} = options;
if (audioMuted) {
// @ts-ignore
state['config.startWithAudioMuted'] = true;
}
if (audioOnlyEnabled) {
// @ts-ignore
state['config.startAudioOnly'] = true;
}
if (skipPrejoin) {
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
// @ts-ignore
state['config.prejoinConfig.enabled'] = false;
}
if (videoMuted) {
// @ts-ignore
state['config.startWithVideoMuted'] = true;
}
const params = new URLSearchParams(locationURL.hash);
for (const [ key, value ] of params) {

View File

@@ -14,10 +14,15 @@ export * from './functions.any';
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
* @param {URL} locationURL - The location URL.
* @param {Object} options: - Config options {
* audioMuted: boolean | undefined
* audioOnlyEnabled: boolean | undefined,
* skipPrejoin: boolean | undefined,
* videoMuted: boolean | undefined
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
@@ -25,11 +30,23 @@ export * from './functions.any';
*/
export const getTokenAuthUrl = (
config: IConfig,
locationURL: URL,
options: {
audioMuted: boolean | undefined;
audioOnlyEnabled: boolean | undefined;
skipPrejoin: boolean | undefined;
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
// eslint-disable-next-line max-params
locationURL: URL): Promise<string | undefined> => {
tenant: string | undefined): Promise<string | undefined> => {
const {
audioMuted = false,
audioOnlyEnabled = false,
skipPrejoin = false,
videoMuted = false
} = options;
let url = config.tokenAuthUrl;
@@ -38,7 +55,17 @@ export const getTokenAuthUrl = (
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin, locationURL);
const state = _getTokenAuthState(
locationURL,
{
audioMuted,
audioOnlyEnabled,
skipPrejoin,
videoMuted
},
roomName,
tenant
);
// Append ios=true or android=true to the token URL.
// @ts-ignore

View File

@@ -32,10 +32,15 @@ function _cryptoRandom() {
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
* @param {URL} locationURL - The current location URL.
* @param {URL} locationURL - The location URL.
* @param {Object} options: - Config options {
* audioMuted: boolean | undefined
* audioOnlyEnabled: boolean | undefined,
* skipPrejoin: boolean | undefined,
* videoMuted: boolean | undefined
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
@@ -43,11 +48,23 @@ function _cryptoRandom() {
*/
export const getTokenAuthUrl = (
config: IConfig,
locationURL: URL,
options: {
audioMuted: boolean | undefined;
audioOnlyEnabled: boolean | undefined;
skipPrejoin: boolean | undefined;
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
// eslint-disable-next-line max-params
locationURL: URL): Promise<string | undefined> => {
tenant: string | undefined): Promise<string | undefined> => {
const {
audioMuted = false,
audioOnlyEnabled = false,
skipPrejoin = false,
videoMuted = false
} = options;
let url = config.tokenAuthUrl;
@@ -56,7 +73,17 @@ export const getTokenAuthUrl = (
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin, locationURL);
const state = _getTokenAuthState(
locationURL,
{
audioMuted,
audioOnlyEnabled,
skipPrejoin,
videoMuted
},
roomName,
tenant
);
if (browser.isElectron()) {
// @ts-ignore

View File

@@ -13,7 +13,9 @@ import {
JitsiConferenceErrors,
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media/constants';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { isLocalTrackMuted } from '../base/tracks/functions.any';
import { parseURIString } from '../base/util/uri';
import { openLogoutDialog } from '../settings/actions';
@@ -257,6 +259,9 @@ function _handleLogin({ dispatch, getState }: IStore) {
const room = state['features/base/conference'].room;
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
if (!room) {
logger.warn('Cannot handle login, room is undefined!');
@@ -270,7 +275,18 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}
getTokenAuthUrl(config, room, tenant, true, locationURL)
getTokenAuthUrl(
config,
locationURL,
{
audioMuted,
audioOnlyEnabled,
skipPrejoin: true,
videoMuted
},
room,
tenant
)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');

View File

@@ -4,7 +4,8 @@ import {
Keyboard,
KeyboardAvoidingView,
Platform,
StatusBar
StatusBar,
ViewStyle
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -38,9 +39,9 @@ interface IProps {
hasBottomTextInput: boolean;
/**
* Is the screen rendering a tab navigator?
* Is the screen header having an extra height?
*/
hasTabNavigator: boolean;
hasExtraHeaderHeight?: boolean;
/**
* Additional style to be appended to the KeyboardAvoidingView.
@@ -54,8 +55,8 @@ const JitsiKeyboardAvoidingView = (
children,
contentContainerStyle,
disableForcedKeyboardDismiss,
hasTabNavigator,
hasBottomTextInput,
hasExtraHeaderHeight,
style
}: IProps) => {
const headerHeight = useHeaderHeight();
@@ -69,13 +70,13 @@ const JitsiKeyboardAvoidingView = (
}, [ insets.bottom ]);
const tabNavigatorPadding
= hasTabNavigator ? headerHeight : 0;
const extraHeaderHeight
= hasExtraHeaderHeight ? headerHeight : 0;
const extraBottomPadding
= addBottomPadding ? bottomPadding : 0;
const noNotchDevicePadding = extraBottomPadding || 10;
const iosVerticalOffset
= headerHeight + noNotchDevicePadding + tabNavigatorPadding;
= headerHeight + noNotchDevicePadding + extraHeaderHeight;
const androidVerticalOffset = hasBottomTextInput
? headerHeight + Number(StatusBar.currentHeight) : headerHeight;
@@ -86,7 +87,7 @@ const JitsiKeyboardAvoidingView = (
return (
<KeyboardAvoidingView
behavior = { Platform.OS === 'ios' ? 'padding' : 'height' }
contentContainerStyle = { contentContainerStyle }
contentContainerStyle = { contentContainerStyle as ViewStyle }
enabled = { true }
keyboardVerticalOffset = {
Platform.OS === 'ios'
@@ -95,7 +96,7 @@ const JitsiKeyboardAvoidingView = (
}
onResponderRelease = { onRelease }
onStartShouldSetResponder = { shouldSetResponse }
style = { style }>
style = { style as ViewStyle }>
{ children }
</KeyboardAvoidingView>
);

View File

@@ -40,9 +40,9 @@ interface IProps {
hasBottomTextInput?: boolean;
/**
* Is the screen rendering a tab navigator?
* Is the screen header having an extra height?
*/
hasTabNavigator?: boolean;
hasExtraHeaderHeight?: boolean;
/**
* Insets for the SafeAreaView.
@@ -61,8 +61,8 @@ const JitsiScreen = ({
children,
disableForcedKeyboardDismiss = false,
footerComponent,
hasTabNavigator = false,
hasBottomTextInput = false,
hasExtraHeaderHeight = false,
safeAreaInsets = [ 'left', 'right' ],
style
}: IProps) => {
@@ -72,7 +72,7 @@ const JitsiScreen = ({
contentContainerStyle = { contentContainerStyle }
disableForcedKeyboardDismiss = { disableForcedKeyboardDismiss }
hasBottomTextInput = { hasBottomTextInput }
hasTabNavigator = { hasTabNavigator }
hasExtraHeaderHeight = { hasExtraHeaderHeight }
style = { style }>
<SafeAreaView
edges = { safeAreaInsets }

View File

@@ -11,7 +11,6 @@ const button = {
const buttonLabel = {
...BaseTheme.typography.bodyShortBold,
lineHeight: 14,
textTransform: 'capitalize'
};

View File

@@ -65,7 +65,7 @@ class Chat extends Component<IProps> {
<ChatInputBar onSend = { this._onSendMessage } />
}
hasBottomTextInput = { true }
hasTabNavigator = { true }
hasExtraHeaderHeight = { true }
style = { styles.chatContainer }>
{/* @ts-ignore */}
<MessageContainer messages = { _messages } />

View File

@@ -12,7 +12,7 @@ import {
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect, useDispatch } from 'react-redux';
import { appNavigate } from '../../../app/actions';
import { appNavigate } from '../../../app/actions.native';
import { IReduxState, IStore } from '../../../app/types';
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
@@ -41,15 +41,15 @@ import { navigate } from '../../../mobile/navigation/components/conference/Confe
import { screen } from '../../../mobile/navigation/routes';
import { setPictureInPictureEnabled } from '../../../mobile/picture-in-picture/functions';
import Captions from '../../../subtitles/components/native/Captions';
import { setToolboxVisible } from '../../../toolbox/actions';
import { setToolboxVisible } from '../../../toolbox/actions.native';
import Toolbox from '../../../toolbox/components/native/Toolbox';
import { isToolboxVisible } from '../../../toolbox/functions';
import { isToolboxVisible } from '../../../toolbox/functions.native';
import {
AbstractConference,
abstractMapStateToProps
} from '../AbstractConference';
import type { AbstractProps } from '../AbstractConference';
import { isConnecting } from '../functions';
import { isConnecting } from '../functions.native';
import AlwaysOnLabels from './AlwaysOnLabels';
import ExpandedLabelPopup from './ExpandedLabelPopup';
@@ -230,7 +230,9 @@ class Conference extends AbstractConference<IProps, State> {
*/
componentDidUpdate(prevProps: IProps) {
const {
_showLobby
_audioOnlyEnabled,
_showLobby,
_startCarMode
} = this.props;
if (!prevProps._showLobby && _showLobby) {
@@ -238,6 +240,10 @@ class Conference extends AbstractConference<IProps, State> {
}
if (prevProps._showLobby && !_showLobby) {
if (_audioOnlyEnabled && _startCarMode) {
return;
}
navigate(screen.conference.main);
}
}

View File

@@ -34,6 +34,7 @@ import {
showSharedVideoMenu
} from '../../../participants-pane/actions.native';
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
import { shouldDisplayTileView } from '../../../video-layout/functions.native';
import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
import AudioMutedIndicator from './AudioMutedIndicator';
@@ -43,6 +44,7 @@ import RaisedHandIndicator from './RaisedHandIndicator';
import ScreenShareIndicator from './ScreenShareIndicator';
import styles, { AVATAR_SIZE } from './styles';
/**
* Thumbnail component's property types.
*/
@@ -111,6 +113,8 @@ interface IProps {
*/
_renderModeratorIndicator: boolean;
_shouldDisplayTileView: boolean;
/**
* The video track that will be displayed in the thumbnail.
*/
@@ -206,14 +210,25 @@ class Thumbnail extends PureComponent<IProps> {
_fakeParticipant,
_isScreenShare: isScreenShare,
_isVirtualScreenshare,
_renderModeratorIndicator: renderModeratorIndicator,
_participantId: participantId,
_pinned,
_renderModeratorIndicator: renderModeratorIndicator,
_shouldDisplayTileView,
renderDisplayName,
tileView
} = this.props;
const indicators = [];
let bottomIndicatorsContainerStyle;
if (_shouldDisplayTileView) {
bottomIndicatorsContainerStyle = styles.bottomIndicatorsContainer;
} else if (audioMuted || renderModeratorIndicator) {
bottomIndicatorsContainerStyle = styles.bottomIndicatorsContainer;
} else {
bottomIndicatorsContainerStyle = null;
}
if (!_fakeParticipant || _isVirtualScreenshare) {
indicators.push(<View
key = 'top-left-indicators'
@@ -228,10 +243,9 @@ class Thumbnail extends PureComponent<IProps> {
</View>);
indicators.push(<Container
key = 'bottom-indicators'
style = { styles.thumbnailIndicatorContainer }>
style = { styles.thumbnailIndicatorContainer as StyleType }>
<Container
style = { ((audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer
) as StyleType }>
style = { bottomIndicatorsContainerStyle as StyleType }>
{ audioMuted && !_isVirtualScreenshare && <AudioMutedIndicator /> }
{ !tileView && _pinned && <PinnedIndicator />}
{ renderModeratorIndicator && !_isVirtualScreenshare && <ModeratorIndicator />}
@@ -422,6 +436,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_raisedHand: hasRaisedHand(participant),
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator,
_shouldDisplayTileView: shouldDisplayTileView(state),
_videoTrack: videoTrack
};
}

View File

@@ -144,8 +144,8 @@ export default {
},
bottomIndicatorsContainer: {
padding: 2,
flexDirection: 'row'
flexDirection: 'row',
padding: BaseTheme.spacing[1]
},
thumbnailTopLeftIndicatorContainer: {

View File

@@ -29,7 +29,10 @@ class LobbyChatScreen extends
const { _lobbyChatMessages } = this.props;
return (
<JitsiScreen style = { styles.lobbyChatWrapper }>
<JitsiScreen
hasBottomTextInput = { true }
hasExtraHeaderHeight = { true }
style = { styles.lobbyChatWrapper }>
{/* @ts-ignore */}
<MessageContainer messages = { _lobbyChatMessages } />
<ChatInputBar onSend = { this._onSendMessage } />

View File

@@ -4,10 +4,7 @@ export default {
lobbyChatWrapper: {
backgroundColor: BaseTheme.palette.ui01,
alignItems: 'stretch',
flexDirection: 'column',
justifyItems: 'center',
height: '100%'
flex: 1
},
passwordJoinButtons: {

View File

@@ -2,7 +2,7 @@ import { NativeModules } from 'react-native';
/**
* Determimes if the ExternalAPI native module is available.
* Determines if the ExternalAPI native module is available.
*
* @returns {boolean} If yes {@code true} otherwise {@code false}.
*/

View File

@@ -1,3 +1,5 @@
import { NativeModules } from 'react-native';
import { getAppProp } from '../../base/app/functions';
import {
CONFERENCE_BLURRED,
@@ -6,8 +8,10 @@ import {
CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN
} from '../../base/conference/actionTypes';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
import { READY_TO_CLOSE } from '../external-api/actionTypes';
import { participantToParticipantInfo } from '../external-api/functions';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
@@ -15,9 +19,12 @@ import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
import { isExternalAPIAvailable } from './functions';
const externalAPIEnabled = isExternalAPIAvailable();
const { JMOngoingConference } = NativeModules;
/**
* Check if native modules are being used or not. If not then the init of middleware doesn't happen.
* Check if native modules are being used or not.
* If not, then the init of middleware doesn't happen.
*/
!externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
const result = next(action);
@@ -25,6 +32,12 @@ const externalAPIEnabled = isExternalAPIAvailable();
const rnSdkHandlers = getAppProp(store, 'rnSdkHandlers');
switch (type) {
case SET_AUDIO_MUTED:
rnSdkHandlers?.onAudioMutedChanged && rnSdkHandlers?.onAudioMutedChanged(action.muted);
break;
case SET_VIDEO_MUTED:
rnSdkHandlers?.onVideoMutedChanged && rnSdkHandlers?.onVideoMutedChanged(Boolean(action.muted));
break;
case CONFERENCE_BLURRED:
rnSdkHandlers?.onConferenceBlurred && rnSdkHandlers?.onConferenceBlurred();
break;
@@ -56,5 +69,21 @@ const externalAPIEnabled = isExternalAPIAvailable();
}
return result;
}
});
/**
* Check if native modules are being used or not.
*/
!externalAPIEnabled && StateListenerRegistry.register(
state => state['features/base/conference'].conference,
(conference, previousConference) => {
if (!conference) {
JMOngoingConference.abort();
} else if (conference && !previousConference) {
JMOngoingConference.launch();
} else if (conference !== previousConference) {
JMOngoingConference.abort();
JMOngoingConference.launch();
}
}
);

View File

@@ -288,6 +288,6 @@ export default {
visitorsLabel: {
...BaseTheme.typography.heading6,
color: BaseTheme.palette.warning02,
marginLeft: BaseTheme.spacing[3]
marginLeft: BaseTheme.spacing[2]
}
};

View File

@@ -1,8 +1,6 @@
import React, { useCallback } from 'react';
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
import { useSelector } from 'react-redux';
import { getLocalParticipant } from '../../../base/participants/functions';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import AbstractPollResults from '../AbstractPollResults';
@@ -20,6 +18,7 @@ const PollResults = (props: AbstractProps) => {
const {
answers,
changeVote,
creatorName,
haveVoted,
question,
showDetails,
@@ -88,7 +87,6 @@ const PollResults = (props: AbstractProps) => {
);
}, [ showDetails ]);
const localParticipant = useSelector(getLocalParticipant);
/* eslint-disable react/jsx-no-bind */
@@ -96,7 +94,7 @@ const PollResults = (props: AbstractProps) => {
<View>
<Text style = { dialogStyles.questionText as TextStyle } >{ question }</Text>
<Text style = { dialogStyles.questionOwnerText as TextStyle } >
{ t('polls.by', { name: localParticipant?.name }) }
{ t('polls.by', { name: creatorName }) }
</Text>
<FlatList
data = { answers }

View File

@@ -48,7 +48,7 @@ const PollsPane = (props: AbstractProps) => {
<JitsiScreen
contentContainerStyle = { chatStyles.pollPane as StyleType }
disableForcedKeyboardDismiss = { true }
hasTabNavigator = { true }
hasExtraHeaderHeight = { true }
style = { chatStyles.pollPaneContainer as StyleType }>
{
createMode

View File

@@ -83,7 +83,8 @@ function _appWillMount({ dispatch, getState }: IStore, next: Function, action: A
* @returns {*} The result returned by {@code next(action)}.
*/
function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
const { doNotStoreRoom } = getState()['features/base/config'];
const state = getState();
const { doNotStoreRoom } = state['features/base/config'];
if (!doNotStoreRoom && !inIframe()) {
let locationURL;
@@ -100,13 +101,17 @@ function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, ac
* JITSI_CONFERENCE_URL_KEY so we cannot call it and must use the other way.
*/
if (typeof APP === 'undefined') {
locationURL = action.conference[JITSI_CONFERENCE_URL_KEY];
const { conference } = action;
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
locationURL = conference && conference[JITSI_CONFERENCE_URL_KEY];
} else {
locationURL = getState()['features/base/connection'].locationURL;
locationURL = state['features/base/connection'].locationURL;
}
dispatch(
_updateConferenceDuration(
locationURL));
locationURL
));
}
return next(action);