mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-07 23:32:29 +00:00
Compare commits
39 Commits
android-23
...
7516
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33fc6e2f3f | ||
|
|
a95eaa6c2e | ||
|
|
5a3947bb23 | ||
|
|
f84a561d9e | ||
|
|
295878ffff | ||
|
|
609942654a | ||
|
|
60ad0196c3 | ||
|
|
caea6966ef | ||
|
|
d4c269f7cb | ||
|
|
54a1ee53b4 | ||
|
|
2c51e8ac06 | ||
|
|
3cbd69eef2 | ||
|
|
ee539644d8 | ||
|
|
465263bc97 | ||
|
|
1def65eb90 | ||
|
|
746be98bfc | ||
|
|
99b58dd318 | ||
|
|
df3ef0d895 | ||
|
|
83e4042668 | ||
|
|
c6e87568b6 | ||
|
|
0170c65c7b | ||
|
|
a7c1ccec71 | ||
|
|
1adbebf9dc | ||
|
|
9d68cb52b3 | ||
|
|
44272b650c | ||
|
|
5ce96d379a | ||
|
|
173c5fe430 | ||
|
|
e10595c3ed | ||
|
|
9138f56701 | ||
|
|
974e2a5106 | ||
|
|
509cf661f5 | ||
|
|
25fdea9984 | ||
|
|
9979e470fc | ||
|
|
2a492f5036 | ||
|
|
baf1f01e44 | ||
|
|
1f8dc944e3 | ||
|
|
dc07c6fede | ||
|
|
94a63f8aea | ||
|
|
a47cb595db |
@@ -10,7 +10,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.1'
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
}
|
||||
@@ -18,21 +18,16 @@ buildscript {
|
||||
|
||||
ext {
|
||||
kotlinVersion = "1.7.0"
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
buildToolsVersion = "33.0.2"
|
||||
compileSdkVersion = 33
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 32
|
||||
targetSdkVersion = 33
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
if (System.properties['os.arch'] == "aarch64") {
|
||||
// For M1 Users we need to use the NDK 24 which added support for aarch64
|
||||
ndkVersion = "24.0.8215888"
|
||||
} else {
|
||||
// Otherwise we default to the side-by-side NDK version from AGP.
|
||||
ndkVersion = "21.4.7075529"
|
||||
}
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// The Maven artifact groupId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -130,6 +130,7 @@ import {
|
||||
isUserInteractionRequiredForUnmute
|
||||
} from './react/features/base/tracks/functions';
|
||||
import { downloadJSON } from './react/features/base/util/downloadJSON';
|
||||
import { openLeaveReasonDialog } from './react/features/conference/actions.web';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker/actions';
|
||||
import { appendSuffix } from './react/features/display-name/functions';
|
||||
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
|
||||
@@ -2428,9 +2429,10 @@ export default {
|
||||
/**
|
||||
* Disconnect from the conference and optionally request user feedback.
|
||||
* @param {boolean} [requestFeedback=false] if user feedback should be
|
||||
* @param {string} [hangupReason] the reason for leaving the meeting
|
||||
* requested
|
||||
*/
|
||||
hangup(requestFeedback = false) {
|
||||
async hangup(requestFeedback = false, hangupReason) {
|
||||
APP.store.dispatch(disableReceiver());
|
||||
|
||||
this._stopProxyConnection();
|
||||
@@ -2447,36 +2449,33 @@ export default {
|
||||
|
||||
APP.UI.removeAllListeners();
|
||||
|
||||
let requestFeedbackPromise;
|
||||
let feedbackResult = {};
|
||||
|
||||
if (requestFeedback) {
|
||||
requestFeedbackPromise
|
||||
= APP.store.dispatch(maybeOpenFeedbackDialog(room))
|
||||
|
||||
// false because the thank you dialog shouldn't be displayed
|
||||
.catch(() => Promise.resolve(false));
|
||||
} else {
|
||||
requestFeedbackPromise = Promise.resolve(true);
|
||||
try {
|
||||
feedbackResult = await APP.store.dispatch(maybeOpenFeedbackDialog(room, hangupReason));
|
||||
} catch (err) { // eslint-disable-line no-empty
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
requestFeedbackPromise,
|
||||
this.leaveRoom()
|
||||
])
|
||||
.then(values => {
|
||||
this._room = undefined;
|
||||
room = undefined;
|
||||
if (!feedbackResult.wasDialogShown && hangupReason) {
|
||||
await APP.store.dispatch(openLeaveReasonDialog(hangupReason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
|
||||
* and let the page take care of sending the message, since there will be
|
||||
* a redirect to the page regardlessly.
|
||||
*/
|
||||
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
APP.store.dispatch(maybeRedirectToWelcomePage(values[0]));
|
||||
});
|
||||
await this.leaveRoom();
|
||||
|
||||
this._room = undefined;
|
||||
room = undefined;
|
||||
|
||||
/**
|
||||
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
|
||||
* and let the page take care of sending the message, since there will be
|
||||
* a redirect to the page anyway.
|
||||
*/
|
||||
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
APP.store.dispatch(maybeRedirectToWelcomePage(feedbackResult));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -1015,6 +1015,10 @@ var config = {
|
||||
// The Amplitude APP Key:
|
||||
// amplitudeAPPKey: '<APP_KEY>',
|
||||
|
||||
// Enables Amplitude UTM tracking:
|
||||
// Default value is false.
|
||||
// amplitudeIncludeUTM: false,
|
||||
|
||||
// Obfuscates room name sent to analytics (amplitude, rtcstats)
|
||||
// Default value is false.
|
||||
// obfuscateRoomName: false,
|
||||
@@ -1572,6 +1576,8 @@ var config = {
|
||||
// disableFilmstripAutohiding: false,
|
||||
|
||||
// filmstrip: {
|
||||
// // Disable the vertical/horizonal filmstrip.
|
||||
// disabled: false,
|
||||
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
|
||||
// // (width, tiles aspect ratios) through the interfaceConfig options.
|
||||
// disableResizable: false,
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
.drawer-portal {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 351;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
||||
&.notification-portal {
|
||||
z-index: 901;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-portal::after {
|
||||
content: '';
|
||||
background-color: #141414;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.drawer-menu-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.drawer-menu {
|
||||
overflow-y: auto;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
&#{&} .overflow-menu {
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
height: calc(80vh - 144px - 64px);
|
||||
overflow-y: auto;
|
||||
|
||||
.overflow-menu-item {
|
||||
box-sizing: border-box;
|
||||
height: 48px;
|
||||
padding: 12px 16px;
|
||||
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,3 @@
|
||||
.participants_pane {
|
||||
background-color: #141414;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
width: 315px;
|
||||
z-index: $zindex0;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 600;
|
||||
height: 100%;
|
||||
width: 315px;
|
||||
|
||||
& > *:first-child,
|
||||
& > *:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 580px) {
|
||||
.participants_pane {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
&-dominant-speaker {
|
||||
background-color: #1EC26A;
|
||||
|
||||
@@ -73,7 +73,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'modals/invite/invite_more';
|
||||
@import 'modals/security/security';
|
||||
@import 'responsive';
|
||||
@import 'drawer';
|
||||
@import 'participants-pane';
|
||||
@import 'reactions-menu';
|
||||
@import 'plan-limit';
|
||||
|
||||
157
ios/Podfile.lock
157
ios/Podfile.lock
@@ -3,11 +3,11 @@ PODS:
|
||||
- amplitude-react-native (2.7.0):
|
||||
- Amplitude (= 8.7.1)
|
||||
- React-Core
|
||||
- AppAuth (1.6.1):
|
||||
- AppAuth/Core (= 1.6.1)
|
||||
- AppAuth/ExternalUserAgent (= 1.6.1)
|
||||
- AppAuth/Core (1.6.1)
|
||||
- AppAuth/ExternalUserAgent (1.6.1):
|
||||
- AppAuth (1.6.2):
|
||||
- AppAuth/Core (= 1.6.2)
|
||||
- AppAuth/ExternalUserAgent (= 1.6.2)
|
||||
- AppAuth/Core (1.6.2)
|
||||
- AppAuth/ExternalUserAgent (1.6.2):
|
||||
- AppAuth/Core
|
||||
- boost (1.76.0)
|
||||
- CocoaLumberjack (3.7.2):
|
||||
@@ -103,56 +103,59 @@ PODS:
|
||||
- GoogleUtilities/Network (~> 7.7)
|
||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
||||
- nanopb (~> 2.30908.0)
|
||||
- GoogleDataTransport (9.2.2):
|
||||
- GoogleDataTransport (9.2.5):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleSignIn (6.2.4):
|
||||
- GoogleSignIn (7.0.0):
|
||||
- AppAuth (~> 1.5)
|
||||
- GTMAppAuth (~> 1.3)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.1):
|
||||
- GTMAppAuth (< 3.0, >= 1.3)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Environment (7.11.1):
|
||||
- GoogleUtilities/Environment (7.11.5):
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.11.1):
|
||||
- GoogleUtilities/Logger (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/MethodSwizzler (7.11.1):
|
||||
- GoogleUtilities/MethodSwizzler (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network (7.11.1):
|
||||
- GoogleUtilities/Network (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (7.11.1)"
|
||||
- GoogleUtilities/Reachability (7.11.1):
|
||||
- "GoogleUtilities/NSData+zlib (7.11.5)"
|
||||
- GoogleUtilities/Reachability (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/UserDefaults (7.11.1):
|
||||
- GoogleUtilities/UserDefaults (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMAppAuth (1.3.1):
|
||||
- GTMAppAuth (2.0.0):
|
||||
- AppAuth/Core (~> 1.6)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (2.3.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (3.1.1)
|
||||
- JitsiWebRTC (111.0.2)
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
- libwebp/webp (= 1.2.4)
|
||||
- libwebp/demux (1.2.4):
|
||||
- libwebp (1.3.1):
|
||||
- libwebp/demux (= 1.3.1)
|
||||
- libwebp/mux (= 1.3.1)
|
||||
- libwebp/sharpyuv (= 1.3.1)
|
||||
- libwebp/webp (= 1.3.1)
|
||||
- libwebp/demux (1.3.1):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.2.4):
|
||||
- libwebp/mux (1.3.1):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.4)
|
||||
- libwebp/sharpyuv (1.3.1)
|
||||
- libwebp/webp (1.3.1):
|
||||
- libwebp/sharpyuv
|
||||
- nanopb (2.30908.0):
|
||||
- nanopb/decode (= 2.30908.0)
|
||||
- nanopb/encode (= 2.30908.0)
|
||||
- nanopb/decode (2.30908.0)
|
||||
- nanopb/encode (2.30908.0)
|
||||
- ObjectiveDropboxOfficial (6.2.3)
|
||||
- PromisesObjC (2.2.0)
|
||||
- PromisesSwift (2.2.0):
|
||||
- PromisesObjC (= 2.2.0)
|
||||
- PromisesObjC (2.3.1)
|
||||
- PromisesSwift (2.3.1):
|
||||
- PromisesObjC (= 2.3.1)
|
||||
- RCT-Folly (2021.06.28.00-v2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -364,36 +367,34 @@ PODS:
|
||||
- glog
|
||||
- react-native-background-timer (2.4.1):
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.7.2):
|
||||
- react-native-get-random-values (1.9.0):
|
||||
- React-Core
|
||||
- react-native-keep-awake (4.0.0):
|
||||
- React
|
||||
- react-native-netinfo (7.1.7):
|
||||
- react-native-netinfo (9.4.1):
|
||||
- React-Core
|
||||
- react-native-orientation-locker (1.5.0):
|
||||
- React-Core
|
||||
- react-native-pager-view (5.4.9):
|
||||
- react-native-pager-view (6.2.0):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.6.4):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- react-native-performance (5.0.0):
|
||||
- React-Core
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-slider (4.1.12):
|
||||
- react-native-safe-area-context (4.7.1):
|
||||
- React-Core
|
||||
- react-native-slider (4.4.3):
|
||||
- React-Core
|
||||
- react-native-splash-screen (3.3.0):
|
||||
- React-Core
|
||||
- react-native-video (6.0.0-alpha.1):
|
||||
- react-native-video (6.0.0-alpha.7):
|
||||
- React-Core
|
||||
- react-native-video/Video (= 6.0.0-alpha.1)
|
||||
- react-native-video/Video (6.0.0-alpha.1):
|
||||
- react-native-video/Video (= 6.0.0-alpha.7)
|
||||
- react-native-video/Video (6.0.0-alpha.7):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (111.0.3):
|
||||
- JitsiWebRTC (~> 111.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
- react-native-webview (13.5.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.69.11)
|
||||
- React-RCTActionSheet (0.69.11):
|
||||
@@ -463,30 +464,30 @@ PODS:
|
||||
- React-perflogger (= 0.69.11)
|
||||
- RNCalendarEvents (2.2.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.17.3):
|
||||
- RNCAsyncStorage (1.19.3):
|
||||
- React-Core
|
||||
- RNCClipboard (1.5.1):
|
||||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (8.4.8):
|
||||
- RNDeviceInfo (10.9.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.9.0):
|
||||
- React-Core
|
||||
- RNGoogleSignin (9.0.2):
|
||||
- GoogleSignIn (~> 6.2)
|
||||
- RNGoogleSignin (10.0.1):
|
||||
- GoogleSignIn (~> 7.0)
|
||||
- React-Core
|
||||
- RNScreens (3.22.0):
|
||||
- RNScreens (3.24.0):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNSound (0.11.1):
|
||||
- RNSound (0.11.2):
|
||||
- React-Core
|
||||
- RNSound/Core (= 0.11.1)
|
||||
- RNSound/Core (0.11.1):
|
||||
- RNSound/Core (= 0.11.2)
|
||||
- RNSound/Core (0.11.2):
|
||||
- React-Core
|
||||
- RNSVG (12.4.3):
|
||||
- RNSVG (13.13.0):
|
||||
- React-Core
|
||||
- RNWatch (1.0.11):
|
||||
- RNWatch (1.1.0):
|
||||
- React
|
||||
- Yoga (1.14.0)
|
||||
|
||||
@@ -525,6 +526,7 @@ DEPENDENCIES:
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- react-native-performance (from `../node_modules/react-native-performance`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||
@@ -638,6 +640,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-orientation-locker"
|
||||
react-native-pager-view:
|
||||
:path: "../node_modules/react-native-pager-view"
|
||||
react-native-performance:
|
||||
:path: "../node_modules/react-native-performance"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-slider:
|
||||
@@ -702,7 +706,7 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
|
||||
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
|
||||
AppAuth: e48b432bb4ba88b10cb2bcc50d7f3af21e78b9c2
|
||||
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
@@ -720,17 +724,17 @@ SPEC CHECKSUMS:
|
||||
giphy-react-native-sdk: fcda9639f8ca2cc47e0517b6ef11c19359db5f5a
|
||||
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
|
||||
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
|
||||
GoogleDataTransport: 8378d1fa8ac49753ea6ce70d65a7cb70ce5f66e6
|
||||
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
|
||||
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
||||
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
|
||||
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
|
||||
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
|
||||
GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842
|
||||
GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
|
||||
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
|
||||
GTMSessionFetcher: e8647203b65cee28c5f73d0f473d096653945e72
|
||||
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
libwebp: 33dc822fbbf4503668d09f7885bbfedc76c45e96
|
||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
||||
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
|
||||
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
||||
PromisesSwift: cf9eb58666a43bbe007302226e510b16c1e10959
|
||||
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
|
||||
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
|
||||
RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a
|
||||
RCTRequired: 8e9a57dddc8f8e9e816c67c2d2537271a997137a
|
||||
RCTTypeSafety: 2b19e268e2036a2c2f6db6deb1ac03e28b1d607a
|
||||
@@ -746,17 +750,18 @@ SPEC CHECKSUMS:
|
||||
React-jsinspector: 9ca5bf73ed0a195397e45fdbcd507cf7d503c428
|
||||
React-logger: 700340e325f21ba2a2d6413a61ef14268c7360aa
|
||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
||||
react-native-netinfo: 27f287f2d191693f3b9d01a4273137fcf91c3b5d
|
||||
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
|
||||
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
|
||||
react-native-pager-view: 3ee7d4c7697fb3ef788346e834a60cca97ed8540
|
||||
react-native-safe-area-context: 68b07eabfb0d14547d36f6929c0e98d818064f02
|
||||
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
|
||||
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
|
||||
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
|
||||
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-video: 967eead48aaa42c25a9e1d65c3b1ab30762a88df
|
||||
react-native-webrtc: 4d1669c2ed29767fe70b0169428b4466589ecf8b
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
|
||||
React-perflogger: fdee2a0c512167ae4c19c4e230ccf6aa66a6aff0
|
||||
React-RCTActionSheet: 1cf5fef4e372f1c877969710a51bea4bb25e78fe
|
||||
React-RCTAnimation: 73816e3acd1f5e3f00166fc7eedb34f6b112f734
|
||||
@@ -770,16 +775,16 @@ SPEC CHECKSUMS:
|
||||
React-runtimeexecutor: 59407514818b2afbb1d7507e4e1ac834d24b0fbd
|
||||
ReactCommon: b8487da74723562d7368dab27135fd182f00a91c
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af
|
||||
RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
|
||||
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
|
||||
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
|
||||
RNGoogleSignin: 22e468a9474dbcb8618d8847205ad4f0b2575d13
|
||||
RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
|
||||
RNWatch: dae6c858a2051dbdcfb00b9a86cf4d90400263b4
|
||||
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
|
||||
RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7
|
||||
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
|
||||
RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
Yoga: 7f5ad94937ba3fc58c151ad1b7bbada2c275b28e
|
||||
|
||||
PODFILE CHECKSUM: e3579df5272b8b697c9fdc0e55aa0845b189c4dd
|
||||
|
||||
@@ -256,6 +256,7 @@
|
||||
"Share": "Share",
|
||||
"Submit": "Submit",
|
||||
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
|
||||
"WaitingForHostButton": "Wait for moderator",
|
||||
"WaitingForHostTitle": "Waiting for a moderator...",
|
||||
"Yes": "Yes",
|
||||
"accessibilityLabel": {
|
||||
@@ -412,6 +413,7 @@
|
||||
"sendPrivateMessageTitle": "Send privately?",
|
||||
"serviceUnavailable": "Service unavailable",
|
||||
"sessTerminated": "Call terminated",
|
||||
"sessTerminatedReason": "The meeting has been terminated",
|
||||
"sessionRestarted": "Call restarted because of a connection issue.",
|
||||
"shareAudio": "Continue",
|
||||
"shareAudioTitle": "How to share audio",
|
||||
@@ -657,13 +659,13 @@
|
||||
"knockingParticipantList": "Knocking participant list",
|
||||
"lobbyChatStartedNotification": "{{moderator}} started a lobby chat with {{attendee}}",
|
||||
"lobbyChatStartedTitle": "{{moderator}} has started a lobby chat with you.",
|
||||
"lobbyClosed": "The lobby room has been closed.",
|
||||
"nameField": "Enter your name",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "Lobby has been disabled by {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Lobby has been enabled by {{originParticipantName}}",
|
||||
"notificationTitle": "Lobby",
|
||||
"passwordField": "Enter meeting password",
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject",
|
||||
"rejectAll": "Reject all",
|
||||
|
||||
@@ -1141,7 +1141,11 @@ class API {
|
||||
*/
|
||||
_sendEvent(event = {}) {
|
||||
if (this._enabled) {
|
||||
transport.sendEvent(event);
|
||||
try {
|
||||
transport.sendEvent(event);
|
||||
} catch (error) {
|
||||
logger.error('Failed to send and IFrame API event', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1475,11 +1479,43 @@ class API {
|
||||
* @param {Array<string>} args - Array of strings composing the log message.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyLog(logLevel, args) {
|
||||
notifyLog(logLevel, args = []) {
|
||||
if (!Array.isArray(args)) {
|
||||
logger.error('notifyLog received wrong argument types!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Trying to convert arguments to strings. Otherwise in order to send the event the arguments will be formatted
|
||||
// with JSON.stringify which can throw an error because of circular objects and we will lose the whole log.
|
||||
const formattedArguments = [];
|
||||
|
||||
args.forEach(arg => {
|
||||
let formattedArgument = '';
|
||||
|
||||
if (arg instanceof Error) {
|
||||
formattedArgument += `${arg.toString()}: ${arg.stack}`;
|
||||
} else if (typeof arg === 'object') {
|
||||
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
|
||||
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
|
||||
// do something better for parsing the arguments. But since this option for strigify is part of the
|
||||
// public interface and I think it could be useful in some cases I will it for now.
|
||||
try {
|
||||
formattedArgument += JSON.stringify(arg);
|
||||
} catch (error) {
|
||||
formattedArgument += arg;
|
||||
}
|
||||
} else {
|
||||
formattedArgument += arg;
|
||||
}
|
||||
|
||||
formattedArguments.push(formattedArgument);
|
||||
});
|
||||
|
||||
this._sendEvent({
|
||||
name: 'log',
|
||||
logLevel,
|
||||
args
|
||||
args: formattedArguments
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* global APP, interfaceConfig */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import Logger from '@jitsi/logger';
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
@@ -23,6 +24,8 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
|
||||
// Corresponds to animation duration from the animatedFadeIn and animatedFadeOut CSS classes.
|
||||
const FADE_DURATION_MS = 300;
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Returns an array of the video dimensions, so that it keeps it's aspect
|
||||
* ratio and fits available area with it's larger dimension. This method
|
||||
@@ -489,7 +492,9 @@ export class VideoContainer extends LargeContainer {
|
||||
}
|
||||
|
||||
if (this.video) {
|
||||
stream.attach(this.video);
|
||||
stream.attach(this.video).catch(error => {
|
||||
logger.error(`Attaching the remote track ${stream} has failed with `, error);
|
||||
});
|
||||
|
||||
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
|
||||
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
|
||||
|
||||
@@ -23,11 +23,8 @@ const VideoLayout = {
|
||||
/**
|
||||
* Handler for local flip X changed event.
|
||||
*/
|
||||
onLocalFlipXChanged() {
|
||||
onLocalFlipXChanged(localFlipX) {
|
||||
if (largeVideo) {
|
||||
const { store } = APP;
|
||||
const { localFlipX } = store.getState()['features/base/settings'];
|
||||
|
||||
largeVideo.onLocalFlipXChange(localFlipX);
|
||||
}
|
||||
},
|
||||
|
||||
1765
package-lock.json
generated
1765
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -23,19 +23,19 @@
|
||||
"@giphy/react-components": "6.8.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
|
||||
"@jitsi/js-utils": "2.1.2",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@mui/styles": "5.12.0",
|
||||
"@react-native-async-storage/async-storage": "1.17.3",
|
||||
"@react-native-async-storage/async-storage": "1.19.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "9.0.2",
|
||||
"@react-native-community/netinfo": "9.4.1",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.0.1",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
"@react-navigation/material-top-tabs": "6.6.3",
|
||||
@@ -65,7 +65,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/v1678.0.0+77e6803f/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -82,31 +82,31 @@
|
||||
"react-native": "0.69.11",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-callstats": "3.73.22",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"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.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"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-pager-view": "5.4.9",
|
||||
"react-native-paper": "5.1.2",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-safe-area-context": "4.6.4",
|
||||
"react-native-screens": "3.22.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.0.0-alpha.7",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-native-webview": "13.5.1",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-window": "1.8.6",
|
||||
|
||||
1336
react-native-sdk/package-lock.json
generated
1336
react-native-sdk/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,8 @@
|
||||
"url": "git+https://github.com/jitsi/jitsi-meet.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/js-utils": "2.0.5",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
@@ -25,11 +24,10 @@
|
||||
"grapheme-splitter": "1.0.4",
|
||||
"i18n-iso-countries": "6.8.0",
|
||||
"i18next": "17.0.6",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"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/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -39,12 +37,12 @@
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-callstats": "3.73.22",
|
||||
"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-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.4.1",
|
||||
@@ -56,34 +54,34 @@
|
||||
"peerDependencies": {
|
||||
"@amplitude/react-native": "2.7.0",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@react-native-async-storage/async-storage": "1.18.2",
|
||||
"@react-native-async-storage/async-storage": "1.19.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "7.0.4",
|
||||
"@react-native-community/netinfo": "9.4.1",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.0.1",
|
||||
"react-native": "*",
|
||||
"react": "*",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "4.11.1",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-orientation-locker": "https://git@github.com/wonday/react-native-orientation-locker#f483520ea6b64b97002374a9e9f053a5299a062a",
|
||||
"react-native-safe-area-context": "4.4.1",
|
||||
"react-native-screens": "3.22.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"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-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "111.0.1",
|
||||
"react-native-webview": "11.15.1"
|
||||
"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-webview": "13.5.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
@@ -98,4 +96,4 @@
|
||||
"keywords": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
react-native-sdk/prepare_sdk.js
vendored
14
react-native-sdk/prepare_sdk.js
vendored
@@ -58,11 +58,25 @@ function copyFolderRecursiveSync(source, target) {
|
||||
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
|
||||
*/
|
||||
function mergeDependencyVersions() {
|
||||
|
||||
// Updates SDK dependencies to match project dependencies.
|
||||
for (const key in SDKPackageJSON.dependencies) {
|
||||
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
|
||||
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updates SDK peer dependencies.
|
||||
for (const key in packageJSON.dependencies) {
|
||||
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
|
||||
|
||||
// Updates all peer dependencies except react and react-native.
|
||||
if (key !== 'react' && key !== 'react-native') {
|
||||
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.stringify(SDKPackageJSON, null, 4);
|
||||
|
||||
fs.writeFileSync('package.json', data);
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function createHandlers({ getState }: IStore) {
|
||||
} = config;
|
||||
const {
|
||||
amplitudeAPPKey,
|
||||
amplitudeIncludeUTM,
|
||||
blackListedEvents,
|
||||
scriptURLs,
|
||||
googleAnalyticsTrackingId,
|
||||
@@ -92,6 +93,7 @@ export async function createHandlers({ getState }: IStore) {
|
||||
const { group, user } = state['features/base/jwt'];
|
||||
const handlerConstructorOptions = {
|
||||
amplitudeAPPKey,
|
||||
amplitudeIncludeUTM,
|
||||
blackListedEvents,
|
||||
envType: deploymentInfo?.envType || 'dev',
|
||||
googleAnalyticsTrackingId,
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface IEvent {
|
||||
|
||||
interface IOptions {
|
||||
amplitudeAPPKey?: string;
|
||||
amplitudeIncludeUTM?: boolean;
|
||||
blackListedEvents?: string[];
|
||||
envType?: string;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
|
||||
@@ -15,13 +15,18 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
* Creates new instance of the Amplitude analytics handler.
|
||||
*
|
||||
* @param {Object} options -
|
||||
* @param {string} options.amplitudeAPPKey - The Amplitude app key required
|
||||
* by the Amplitude API.
|
||||
* @param {string} options.amplitudeAPPKey - The Amplitude app key required by the Amplitude API.
|
||||
* @param {boolean} options.amplitudeIncludeUTM - Whether to include UTM parameters
|
||||
* in the Amplitude events.
|
||||
*/
|
||||
constructor(options: any) {
|
||||
super(options);
|
||||
|
||||
const { amplitudeAPPKey, user } = options;
|
||||
const {
|
||||
amplitudeAPPKey,
|
||||
amplitudeIncludeUTM: includeUtm = true,
|
||||
user
|
||||
} = options;
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
@@ -43,6 +48,8 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
} else {
|
||||
const amplitudeOptions: any = {
|
||||
includeReferrer: true,
|
||||
includeUtm,
|
||||
saveParamsReferrerOncePerSession: false,
|
||||
onError
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Amplitude } from '@amplitude/react-native';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { getUniqueId } from 'react-native-device-info';
|
||||
|
||||
import logger from '../../logger';
|
||||
|
||||
|
||||
/**
|
||||
* Custom logic for setting the correct device id.
|
||||
@@ -14,11 +17,17 @@ export async function fixDeviceID(amplitude: Amplitude) {
|
||||
const current = await DefaultPreference.get('amplitudeDeviceId');
|
||||
|
||||
if (current) {
|
||||
amplitude.setDeviceId(current);
|
||||
await amplitude.setDeviceId(current);
|
||||
} else {
|
||||
const uid = DeviceInfo.getUniqueId();
|
||||
const uid = await getUniqueId();
|
||||
|
||||
amplitude.setDeviceId(uid);
|
||||
DefaultPreference.set('amplitudeDeviceId', uid);
|
||||
if (!uid) {
|
||||
logger.warn('Device ID is not set!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await amplitude.setDeviceId(uid as string);
|
||||
await DefaultPreference.set('amplitudeDeviceId', uid as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../../app/types';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import ConfirmDialog from '../../../base/dialog/components/native/ConfirmDialog';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { cancelWaitForOwner, login } from '../../actions.native';
|
||||
@@ -12,6 +12,11 @@ import { cancelWaitForOwner, login } from '../../actions.native';
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Whether to show alternative cancel button text.
|
||||
*/
|
||||
_alternativeCancelText?: boolean;
|
||||
|
||||
/**
|
||||
* Redux store dispatch function.
|
||||
*/
|
||||
@@ -53,7 +58,7 @@ class WaitForOwnerDialog extends Component<IProps> {
|
||||
render() {
|
||||
return (
|
||||
<ConfirmDialog
|
||||
cancelLabel = 'dialog.Cancel'
|
||||
cancelLabel = { this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }
|
||||
confirmLabel = 'dialog.IamHost'
|
||||
descriptionKey = 'dialog.WaitForHostMsg'
|
||||
onCancel = { this._onCancel }
|
||||
@@ -79,9 +84,23 @@ class WaitForOwnerDialog extends Component<IProps> {
|
||||
*/
|
||||
_onLogin() {
|
||||
this.props.dispatch(login());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(WaitForOwnerDialog));
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated
|
||||
* {@code WaitForOwnerDialog}'s props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_alternativeCancelText: membersOnly && lobbyWaitingForHost
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../../app/types';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import { cancelWaitForOwner, login } from '../../actions.web';
|
||||
@@ -12,6 +12,11 @@ import { cancelWaitForOwner, login } from '../../actions.web';
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether to show alternative cancel button text.
|
||||
*/
|
||||
_alternativeCancelText?: boolean;
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
@@ -71,6 +76,8 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ translationKey:
|
||||
this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }}
|
||||
disableBackdropClose = { true }
|
||||
hideCloseButton = { true }
|
||||
ok = {{ translationKey: 'dialog.IamHost' }}
|
||||
@@ -85,4 +92,20 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(WaitForOwnerDialog));
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated
|
||||
* {@code WaitForOwnerDialog}'s props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_alternativeCancelText: membersOnly && lobbyWaitingForHost
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));
|
||||
|
||||
@@ -2,9 +2,11 @@ import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEve
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { endpointMessageReceived } from '../../subtitles/actions.any';
|
||||
import { setIAmVisitor } from '../../visitors/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { getReplaceParticipant } from '../config/functions';
|
||||
import { hangup } from '../connection/actions';
|
||||
import { connect, disconnect, hangup } from '../connection/actions';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
|
||||
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
|
||||
@@ -73,6 +75,7 @@ import {
|
||||
getConferenceOptions,
|
||||
getConferenceState,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
sendLocalParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -983,3 +986,41 @@ export function setAssumedBandwidthBps(assumedBandwidthBps: number) {
|
||||
assumedBandwidthBps
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to a new visitor node.
|
||||
*
|
||||
* @param {string | undefined} vnode - The vnode to use or undefined if moving back to the main room.
|
||||
* @param {string} focusJid - The focus jid to use.
|
||||
* @param {string} username - The username to use.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function redirect(vnode: string, focusJid: string, username: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { conference, joining } = getState()['features/base/conference'];
|
||||
|
||||
const newConfig = getVisitorOptions(getState, vnode, focusJid, username);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference || joining)))
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,9 +28,10 @@ export const EMAIL_COMMAND = 'email';
|
||||
*/
|
||||
export const JITSI_CONFERENCE_URL_KEY = Symbol('url');
|
||||
|
||||
export const TRIGGER_READY_TO_CLOSE_REASONS = [
|
||||
'The meeting has been terminated'
|
||||
];
|
||||
export const TRIGGER_READY_TO_CLOSE_REASONS = {
|
||||
'dialog.sessTerminatedReason': 'The meeting has been terminated',
|
||||
'lobby.lobbyClosed': 'Lobby room closed.'
|
||||
};
|
||||
|
||||
/**
|
||||
* Conference leave reasons.
|
||||
@@ -39,8 +40,3 @@ export const CONFERENCE_LEAVE_REASONS = {
|
||||
SWITCH_ROOM: 'switch_room',
|
||||
UNRECOVERABLE_ERROR: 'unrecoverable_error'
|
||||
};
|
||||
|
||||
/**
|
||||
* Timeout for properly leaving the conference if it was destroyed.
|
||||
*/
|
||||
export const CONFERENCE_DESTROYED_LEAVE_TIMEOUT = 10000;
|
||||
|
||||
@@ -284,12 +284,12 @@ export function restoreConferenceOptions(stateful: IStateful) {
|
||||
* Override the global config (that is, window.config) with XMPP configuration required to join as a visitor.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store state.
|
||||
* @param {Array<string>} params - The received parameters.
|
||||
* @param {string|undefined} vnode - The received parameters.
|
||||
* @param {string} focusJid - The received parameters.
|
||||
* @param {string|undefined} username - The received parameters.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
const [ vnode, focusJid, username ] = params;
|
||||
|
||||
export function getVisitorOptions(stateful: IStateful, vnode: string, focusJid: string, username: string) {
|
||||
const config = toState(stateful)['features/base/config'];
|
||||
|
||||
if (!config?.hosts) {
|
||||
|
||||
@@ -14,12 +14,10 @@ import { reloadNow } from '../../app/actions';
|
||||
import { IStore } from '../../app/types';
|
||||
import { removeLobbyChatParticipant } from '../../chat/actions.any';
|
||||
import { openDisplayNamePrompt } from '../../display-name/actions';
|
||||
import { readyToClose } from '../../mobile/external-api/actions';
|
||||
import { showErrorNotification, showWarningNotification } from '../../notifications/actions';
|
||||
import { showErrorNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { stopLocalVideoRecording } from '../../recording/actions.any';
|
||||
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager';
|
||||
import { setIAmVisitor } from '../../visitors/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
|
||||
@@ -35,7 +33,6 @@ import {
|
||||
} from '../participants/functions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
|
||||
import { destroyLocalTracks } from '../tracks/actions.any';
|
||||
import { getLocalTracks } from '../tracks/functions.any';
|
||||
|
||||
import {
|
||||
@@ -52,24 +49,17 @@ import {
|
||||
import {
|
||||
authStatusChanged,
|
||||
conferenceFailed,
|
||||
conferenceWillInit,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
leaveConference,
|
||||
setLocalSubject,
|
||||
setSubject
|
||||
} from './actions';
|
||||
import {
|
||||
CONFERENCE_DESTROYED_LEAVE_TIMEOUT,
|
||||
CONFERENCE_LEAVE_REASONS,
|
||||
TRIGGER_READY_TO_CLOSE_REASONS
|
||||
} from './constants';
|
||||
import { CONFERENCE_LEAVE_REASONS } from './constants';
|
||||
import {
|
||||
_addLocalTracksToConference,
|
||||
_removeLocalTracksFromConference,
|
||||
forEachConference,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
restoreConferenceOptions
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -153,25 +143,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
|
||||
// Handle specific failure reasons.
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
const [ reason ] = error.params;
|
||||
|
||||
dispatch(showWarningNotification({
|
||||
description: reason,
|
||||
titleKey: 'dialog.sessTerminated'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
||||
if (TRIGGER_READY_TO_CLOSE_REASONS.includes(reason)) {
|
||||
if (typeof APP === 'undefined') {
|
||||
dispatch(readyToClose());
|
||||
} else {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
setTimeout(() => dispatch(leaveConference()), CONFERENCE_DESTROYED_LEAVE_TIMEOUT);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
|
||||
if (enableForcedReload) {
|
||||
dispatch(showErrorNotification({
|
||||
@@ -227,34 +198,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
const newConfig = getVisitorOptions(getState, error.params);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
break;
|
||||
}
|
||||
|
||||
const [ vnode ] = error.params;
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference)))
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
!error.recoverable
|
||||
|
||||
@@ -1 +1,36 @@
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { notifyConferenceFailed } from '../../conference/actions.native';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { CONFERENCE_FAILED } from './actionTypes';
|
||||
import { conferenceLeft } from './actions';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch } = store;
|
||||
const { error } = action;
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED: {
|
||||
if (error?.name !== JitsiConferenceErrors.CONFERENCE_DESTROYED) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [ reason ] = error.params;
|
||||
|
||||
const reasonKey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
|
||||
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
|
||||
];
|
||||
|
||||
dispatch(notifyConferenceFailed(reasonKey, () => {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import {
|
||||
setPrejoinPageVisibility,
|
||||
setSkipPrejoinOnReload
|
||||
} from '../../prejoin/actions.web';
|
||||
import { hangup } from '../connection/actions.web';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
@@ -12,7 +15,9 @@ import {
|
||||
CONFERENCE_LEFT,
|
||||
KICKED_OUT
|
||||
} from './actionTypes';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
let screenLock: WakeLockSentinel | undefined;
|
||||
@@ -108,6 +113,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
dispatch(setSkipPrejoinOnReload(true));
|
||||
}
|
||||
|
||||
if (errorName === JitsiConferenceErrors.CONFERENCE_DESTROYED) {
|
||||
const [ reason ] = action.error.params;
|
||||
const titlekey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
|
||||
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
|
||||
];
|
||||
|
||||
dispatch(hangup(true, i18next.t(titlekey) || reason));
|
||||
}
|
||||
|
||||
releaseScreenLock();
|
||||
|
||||
break;
|
||||
|
||||
@@ -137,6 +137,7 @@ export interface IConferenceState {
|
||||
followMeEnabled?: boolean;
|
||||
joining?: IJitsiConference;
|
||||
leaving?: IJitsiConference;
|
||||
lobbyWaitingForHost?: boolean;
|
||||
localSubject?: string;
|
||||
locked?: string;
|
||||
membersOnly?: IJitsiConference;
|
||||
@@ -162,6 +163,10 @@ export interface IJitsiConferenceRoom {
|
||||
roomjid: string;
|
||||
}
|
||||
|
||||
interface IConferenceFailedError extends Error {
|
||||
params: Array<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions that contain the conference object, so that it can be
|
||||
* stored for use by other action creators.
|
||||
@@ -274,7 +279,7 @@ function _authStatusChanged(state: IConferenceState,
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
conference: IJitsiConference; error: Error; }) {
|
||||
conference: IJitsiConference; error: IConferenceFailedError; }) {
|
||||
// The current (similar to getCurrentConference in
|
||||
// base/conference/functions.any.js) conference which is joining or joined:
|
||||
const conference_ = state.conference || state.joining;
|
||||
@@ -286,6 +291,7 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
let authRequired;
|
||||
let membersOnly;
|
||||
let passwordRequired;
|
||||
let lobbyWaitingForHost;
|
||||
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED:
|
||||
@@ -293,9 +299,16 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED:
|
||||
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR:
|
||||
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR: {
|
||||
membersOnly = conference;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [ _lobbyJid, _lobbyWaitingForHost ] = error.params;
|
||||
|
||||
lobbyWaitingForHost = _lobbyWaitingForHost;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.PASSWORD_REQUIRED:
|
||||
passwordRequired = conference;
|
||||
@@ -309,6 +322,7 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
error,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
lobbyWaitingForHost,
|
||||
|
||||
/**
|
||||
* The indicator of how the conference/room is locked. If falsy, the
|
||||
@@ -365,6 +379,8 @@ function _conferenceJoined(state: IConferenceState, { conference }: { conference
|
||||
membersOnly: undefined,
|
||||
leaving: undefined,
|
||||
|
||||
lobbyWaitingForHost: undefined,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is locked.
|
||||
*
|
||||
|
||||
@@ -182,6 +182,7 @@ export interface IConfig {
|
||||
_screenshotHistoryRegionUrl?: number;
|
||||
analytics?: {
|
||||
amplitudeAPPKey?: string;
|
||||
amplitudeIncludeUTM?: boolean;
|
||||
blackListedEvents?: string[];
|
||||
disabled?: boolean;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
@@ -401,6 +402,7 @@ export interface IConfig {
|
||||
disableResizable?: boolean;
|
||||
disableStageFilmstrip?: boolean;
|
||||
disableTopPanel?: boolean;
|
||||
disabled?: boolean;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
};
|
||||
firefox_fake_device?: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
@@ -230,6 +230,9 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_REDIRECTED,
|
||||
_onConnectionRedirected);
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
@@ -298,9 +301,28 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {string|undefined} vnode - The vnode to connect to.
|
||||
* @param {string} focusJid - The focus jid to use.
|
||||
* @param {string|undefined} username - The username to use when joining. This is after promotion from
|
||||
* visitor to main participant.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionRedirected(vnode: string, focusJid: string, username: string) {
|
||||
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_REDIRECTED, _onConnectionRedirected);
|
||||
dispatch(redirect(vnode, focusJid, username));
|
||||
}
|
||||
|
||||
// in case of configured http url for conference request we need the room name
|
||||
const name = getBackendSafeRoomName(state['features/base/conference'].room);
|
||||
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
password,
|
||||
name
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,9 +58,10 @@ export function connect(id?: string, password?: string) {
|
||||
*
|
||||
* @param {boolean} [requestFeedback] - Whether to attempt showing a
|
||||
* request for call feedback.
|
||||
* @param {string} [feedbackTitle] - The feedback title.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hangup(requestFeedback = false) {
|
||||
export function hangup(requestFeedback = false, feedbackTitle?: string) {
|
||||
// XXX For web based version we use conference hanging up logic from the old app.
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
if (LocalRecordingManager.isRecordingLocally()) {
|
||||
@@ -76,6 +77,6 @@ export function hangup(requestFeedback = false) {
|
||||
});
|
||||
}
|
||||
|
||||
return APP.conference.hangup(requestFeedback);
|
||||
return APP.conference.hangup(requestFeedback, feedbackTitle);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
import { getLocalTrack } from '../tracks/functions';
|
||||
|
||||
import {
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
@@ -182,13 +184,20 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
case SET_VIDEO_INPUT_DEVICE:
|
||||
case SET_VIDEO_INPUT_DEVICE: {
|
||||
const localTrack = getLocalTrack(store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||
|
||||
// on mobile devices the video stream has to be stopped before replacing it
|
||||
if (isMobileBrowser() && localTrack && !localTrack.muted) {
|
||||
localTrack.jitsiTrack.stopStream();
|
||||
}
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(replaceVideoTrackById(action.deviceId));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPDATE_DEVICE_LIST:
|
||||
logDeviceList(groupDevicesByKind(action.devices));
|
||||
if (areDeviceLabelsInitialized(store.getState())) {
|
||||
|
||||
@@ -18,3 +18,4 @@ export function isMobileBrowser() {
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ import logger from './logger';
|
||||
* @returns {void}
|
||||
*/
|
||||
function onFakeLocalStorageChanged() {
|
||||
console.error(jitsiLocalStorage.serialize([ 'jitsiLocalStorage' ]));
|
||||
|
||||
APP.API.notifyLocalStorageChanged(jitsiLocalStorage.serialize([ 'jitsiLocalStorage' ]));
|
||||
}
|
||||
|
||||
|
||||
@@ -120,9 +120,7 @@ export default class JitsiMeetLogStorage {
|
||||
conference.sendApplicationLog(logMessage);
|
||||
} catch (error) {
|
||||
// NOTE console is intentional here
|
||||
console.error(
|
||||
`Failed to store the logs, msg length: ${logMessage.length}`
|
||||
+ `error: ${JSON.stringify(error)}`);
|
||||
console.error(`Failed to store the logs, msg length: ${logMessage.length} error:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class AudioTrack extends Component<IProps> {
|
||||
/**
|
||||
* Reference to the HTML audio element, stored until the file is ready.
|
||||
*/
|
||||
_ref: HTMLAudioElement | null;
|
||||
_ref: React.RefObject<HTMLAudioElement>;
|
||||
|
||||
/**
|
||||
* The current timeout ID for play() retries.
|
||||
@@ -80,7 +80,7 @@ class AudioTrack extends Component<IProps> {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._errorHandler = this._errorHandler.bind(this);
|
||||
this._setRef = this._setRef.bind(this);
|
||||
this._ref = React.createRef();
|
||||
this._play = this._play.bind(this);
|
||||
}
|
||||
|
||||
@@ -94,19 +94,22 @@ class AudioTrack extends Component<IProps> {
|
||||
componentDidMount() {
|
||||
this._attachTrack(this.props.audioTrack);
|
||||
|
||||
if (this._ref) {
|
||||
if (this._ref?.current) {
|
||||
const audio = this._ref?.current;
|
||||
const { _muted, _volume } = this.props;
|
||||
|
||||
if (typeof _volume === 'number') {
|
||||
this._ref.volume = _volume;
|
||||
audio.volume = _volume;
|
||||
}
|
||||
|
||||
if (typeof _muted === 'boolean') {
|
||||
this._ref.muted = _muted;
|
||||
audio.muted = _muted;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this._ref.addEventListener('error', this._errorHandler);
|
||||
audio.addEventListener('error', this._errorHandler);
|
||||
} else { // This should never happen
|
||||
logger.error(`The react reference is null for AudioTrack ${this.props?.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +124,7 @@ class AudioTrack extends Component<IProps> {
|
||||
this._detachTrack(this.props.audioTrack);
|
||||
|
||||
// @ts-ignore
|
||||
this._ref?.removeEventListener('error', this._errorHandler);
|
||||
this._ref?.current?.removeEventListener('error', this._errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,19 +144,25 @@ class AudioTrack extends Component<IProps> {
|
||||
this._attachTrack(nextProps.audioTrack);
|
||||
}
|
||||
|
||||
if (this._ref) {
|
||||
const currentVolume = this._ref.volume;
|
||||
if (this._ref?.current) {
|
||||
const audio = this._ref?.current;
|
||||
const currentVolume = audio.volume;
|
||||
const nextVolume = nextProps._volume;
|
||||
|
||||
if (typeof nextVolume === 'number' && !isNaN(nextVolume) && currentVolume !== nextVolume) {
|
||||
this._ref.volume = nextVolume;
|
||||
if (nextVolume === 0) {
|
||||
logger.debug(`Setting audio element ${nextProps?.id} volume to 0`);
|
||||
}
|
||||
audio.volume = nextVolume;
|
||||
}
|
||||
|
||||
const currentMuted = this._ref.muted;
|
||||
const currentMuted = audio.muted;
|
||||
const nextMuted = nextProps._muted;
|
||||
|
||||
if (typeof nextMuted === 'boolean' && currentMuted !== nextMuted) {
|
||||
this._ref.muted = nextMuted;
|
||||
logger.debug(`Setting audio element ${nextProps?.id} muted to true`);
|
||||
|
||||
audio.muted = nextMuted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +182,7 @@ class AudioTrack extends Component<IProps> {
|
||||
<audio
|
||||
autoPlay = { autoPlay }
|
||||
id = { id }
|
||||
ref = { this._setRef } />
|
||||
ref = { this._ref } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,12 +194,29 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(track?: ITrack) {
|
||||
const { id } = this.props;
|
||||
|
||||
if (!track?.jitsiTrack) {
|
||||
logger.warn(`Attach is called on audio element ${id} without tracks passed!`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
track.jitsiTrack.attach(this._ref);
|
||||
this._play();
|
||||
if (!this._ref?.current) {
|
||||
logger.warn(`Attempting to attach track ${track?.jitsiTrack} on AudioTrack ${id} without reference!`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
track.jitsiTrack.attach(this._ref.current)
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Attaching the remote track ${track.jitsiTrack} to video with id ${id} has failed with `,
|
||||
error);
|
||||
})
|
||||
.finally(() => {
|
||||
this._play();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,10 +228,10 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_detachTrack(track?: ITrack) {
|
||||
if (this._ref && track && track.jitsiTrack) {
|
||||
if (this._ref?.current && track && track.jitsiTrack) {
|
||||
clearTimeout(this._playTimeout);
|
||||
this._playTimeout = undefined;
|
||||
track.jitsiTrack.detach(this._ref);
|
||||
track.jitsiTrack.detach(this._ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,18 +255,20 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_play(retries = 0) {
|
||||
if (!this._ref) {
|
||||
const { autoPlay, id } = this.props;
|
||||
|
||||
if (!this._ref?.current) {
|
||||
// nothing to play.
|
||||
logger.warn(`Attempting to call play on AudioTrack ${id} without reference!`);
|
||||
|
||||
return;
|
||||
}
|
||||
const { autoPlay, id } = this.props;
|
||||
|
||||
if (autoPlay) {
|
||||
// Ensure the audio gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case the audio may not autoplay.
|
||||
this._ref.play()
|
||||
this._ref.current.play()
|
||||
.then(() => {
|
||||
if (retries !== 0) {
|
||||
// success after some failures
|
||||
@@ -249,7 +277,7 @@ class AudioTrack extends Component<IProps> {
|
||||
logger.info(`Successfully played audio track! retries: ${retries}`);
|
||||
}
|
||||
}, e => {
|
||||
logger.error(`Failed to play audio track! retry: ${retries} ; Error: ${e}`);
|
||||
logger.error(`Failed to play audio track on audio element ${id}! retry: ${retries} ; Error:`, e);
|
||||
|
||||
if (retries < 3) {
|
||||
this._playTimeout = window.setTimeout(() => this._play(retries + 1), 1000);
|
||||
@@ -264,17 +292,6 @@ class AudioTrack extends Component<IProps> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference to the HTML audio element.
|
||||
*
|
||||
* @param {HTMLAudioElement} audioElement - The HTML audio element instance.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setRef(audioElement: HTMLAudioElement | null) {
|
||||
this._ref = audioElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, ReactEventHandler } from 'react';
|
||||
|
||||
import { ITrack } from '../../../tracks/types';
|
||||
import logger from '../../logger';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Video}.
|
||||
@@ -227,13 +228,13 @@ class Video extends Component<IProps> {
|
||||
this._videoElement.onplaying = this._onVideoPlaying;
|
||||
}
|
||||
|
||||
this._attachTrack(this.props.videoTrack);
|
||||
this._attachTrack(this.props.videoTrack).finally(() => {
|
||||
if (this._videoElement && this.props.autoPlay) {
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
|
||||
if (this._videoElement && this.props.autoPlay) {
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
this._videoElement.play()
|
||||
this._videoElement.play()
|
||||
.catch(error => {
|
||||
// Prevent uncaught "DOMException: The play() request was interrupted by a new load request"
|
||||
// when video playback takes long to start and it starts after the component was unmounted.
|
||||
@@ -241,7 +242,8 @@ class Video extends Component<IProps> {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +273,9 @@ class Video extends Component<IProps> {
|
||||
|
||||
if (currentJitsiTrack !== nextJitsiTrack) {
|
||||
this._detachTrack(this.props.videoTrack);
|
||||
this._attachTrack(nextProps.videoTrack);
|
||||
this._attachTrack(nextProps.videoTrack).catch((_error: Error) => {
|
||||
// Ignore the error. We are already logging it.
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.style !== nextProps.style || this.props.className !== nextProps.className) {
|
||||
@@ -321,11 +325,22 @@ class Video extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(videoTrack?: Partial<ITrack>) {
|
||||
const { id } = this.props;
|
||||
|
||||
if (!videoTrack?.jitsiTrack) {
|
||||
return;
|
||||
logger.warn(`Attach is called on video element ${id} without tracks passed!`);
|
||||
|
||||
// returning Promise.resolve just keep the previous logic.
|
||||
// TODO: Check if it make sense to call play on this element or we can just return promise.reject().
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
videoTrack.jitsiTrack.attach(this._videoElement);
|
||||
return videoTrack.jitsiTrack.attach(this._videoElement)
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Attaching the remote track ${videoTrack.jitsiTrack} to video with id ${id} has failed with `,
|
||||
error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button as NativePaperButton,
|
||||
Text,
|
||||
TouchableRipple
|
||||
} from 'react-native-paper';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
import { Button as NativePaperButton, Text } from 'react-native-paper';
|
||||
|
||||
import { BUTTON_MODES, BUTTON_TYPES } from '../../constants.native';
|
||||
import BaseTheme from '../BaseTheme.native';
|
||||
@@ -12,13 +9,13 @@ import { IButtonProps } from '../types';
|
||||
|
||||
import styles from './buttonStyles';
|
||||
|
||||
|
||||
export interface IProps extends IButtonProps {
|
||||
color?: string | undefined;
|
||||
contentStyle?: Object | undefined;
|
||||
labelStyle?: Object | undefined;
|
||||
mode?: any;
|
||||
style?: Object | undefined;
|
||||
useRippleColor?: boolean;
|
||||
}
|
||||
|
||||
const Button: React.FC<IProps> = ({
|
||||
@@ -32,16 +29,12 @@ const Button: React.FC<IProps> = ({
|
||||
mode = BUTTON_MODES.CONTAINED,
|
||||
onClick: onPress,
|
||||
style,
|
||||
type,
|
||||
useRippleColor = true
|
||||
type
|
||||
}: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
const { CONTAINED, TEXT } = BUTTON_MODES;
|
||||
|
||||
const rippleColor
|
||||
= useRippleColor ? BaseTheme.palette.action03Active : 'transparent';
|
||||
|
||||
let buttonLabelStyles;
|
||||
let buttonStyles;
|
||||
let color;
|
||||
@@ -72,17 +65,16 @@ const Button: React.FC<IProps> = ({
|
||||
}
|
||||
|
||||
if (type === TERTIARY) {
|
||||
if (useRippleColor && disabled) {
|
||||
if (disabled) {
|
||||
buttonLabelStyles = styles.buttonLabelTertiaryDisabled;
|
||||
}
|
||||
buttonLabelStyles = styles.buttonLabelTertiary;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
<TouchableHighlight
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { rippleColor }
|
||||
style = { [
|
||||
buttonStyles,
|
||||
style
|
||||
@@ -92,7 +84,7 @@ const Button: React.FC<IProps> = ({
|
||||
buttonLabelStyles,
|
||||
labelStyle
|
||||
] }>{ t(labelKey ?? '') }</Text>
|
||||
</TouchableRipple>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { TouchableRipple } from 'react-native-paper';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import styles from '../../../react/components/native/styles';
|
||||
@@ -22,47 +22,47 @@ const IconButton: React.FC<IIconButtonProps> = ({
|
||||
const { PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
|
||||
let color;
|
||||
let rippleColor;
|
||||
let underlayColor;
|
||||
let iconButtonContainerStyles;
|
||||
|
||||
if (type === PRIMARY) {
|
||||
color = BaseTheme.palette.icon01;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerPrimary;
|
||||
rippleColor = BaseTheme.palette.action01;
|
||||
underlayColor = BaseTheme.palette.action01;
|
||||
} else if (type === SECONDARY) {
|
||||
color = BaseTheme.palette.icon04;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerSecondary;
|
||||
rippleColor = BaseTheme.palette.action02;
|
||||
underlayColor = BaseTheme.palette.action02;
|
||||
} else if (type === TERTIARY) {
|
||||
color = iconColor;
|
||||
iconButtonContainerStyles = styles.iconButtonContainer;
|
||||
rippleColor = BaseTheme.palette.action03;
|
||||
underlayColor = BaseTheme.palette.action03;
|
||||
} else {
|
||||
color = iconColor;
|
||||
rippleColor = tapColor;
|
||||
underlayColor = tapColor;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
color = BaseTheme.palette.icon03;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerDisabled;
|
||||
rippleColor = 'transparent';
|
||||
underlayColor = 'transparent';
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
<TouchableHighlight
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { rippleColor }
|
||||
style = { [
|
||||
iconButtonContainerStyles,
|
||||
style
|
||||
] }>
|
||||
] }
|
||||
underlayColor = { underlayColor }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 20 || size }
|
||||
src = { src } />
|
||||
</TouchableRipple>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ export interface IProps {
|
||||
onClose?: () => void;
|
||||
size?: 'large' | 'medium';
|
||||
submit?: () => void;
|
||||
testId?: string;
|
||||
title?: string;
|
||||
titleKey?: string;
|
||||
}
|
||||
@@ -152,6 +153,7 @@ const BaseDialog = ({
|
||||
onClose,
|
||||
size = 'medium',
|
||||
submit,
|
||||
testId,
|
||||
title,
|
||||
titleKey
|
||||
}: IProps) => {
|
||||
@@ -179,7 +181,9 @@ const BaseDialog = ({
|
||||
}, [ handleKeyDown ]);
|
||||
|
||||
return (
|
||||
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
|
||||
<div
|
||||
className = { cx(classes.container, isUnmounting && 'unmount') }
|
||||
data-testid = { testId }>
|
||||
<div className = { classes.backdrop } />
|
||||
<FocusOn
|
||||
className = { classes.focusLock }
|
||||
|
||||
@@ -96,6 +96,7 @@ const Dialog = ({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
size,
|
||||
testId,
|
||||
title,
|
||||
titleKey
|
||||
}: IDialogProps) => {
|
||||
@@ -127,6 +128,7 @@ const Dialog = ({
|
||||
onClose = { onClose }
|
||||
size = { size }
|
||||
submit = { submit }
|
||||
testId = { testId }
|
||||
title = { title }
|
||||
titleKey = { titleKey }>
|
||||
<div className = { classes.header }>
|
||||
|
||||
@@ -3,8 +3,8 @@ import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getParticipantDisplayName, isLocalParticipantModerator } from '../../base/participants/functions';
|
||||
import { setLobbyChatActiveState } from '../actions.any';
|
||||
import { setPrivateMessageRecipient } from '../actions.web';
|
||||
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../actions.any';
|
||||
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
import AlertDialog from '../base/dialog/components/native/AlertDialog';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
|
||||
@@ -34,6 +34,38 @@ export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
*
|
||||
* @param {string} reasonKey - The translation key for the reason why the conference failed.
|
||||
* @param {?Function} submit - The function to execute after submiting the dialog.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (!reasonKey) {
|
||||
submit?.();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// we have to push the opening of the dialog to the queue
|
||||
// so that we make sure it will be visible after the events
|
||||
// of conference destroyed are done
|
||||
setTimeout(() => dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: reasonKey
|
||||
},
|
||||
params: {
|
||||
},
|
||||
onSubmit: () => {
|
||||
submit?.();
|
||||
dispatch(hideDialog(AlertDialog));
|
||||
}
|
||||
})));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses calendar notification about next or ongoing event.
|
||||
*
|
||||
|
||||
@@ -1,41 +1,26 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { configureInitialDevices } from '../base/devices/actions.web';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { getBackendSafeRoomName } from '../base/util/uri';
|
||||
import { showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
|
||||
|
||||
import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
|
||||
import LeaveReasonDialog from './components/web/LeaveReasonDialog.web';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
* Opens {@code LeaveReasonDialog}.
|
||||
*
|
||||
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
|
||||
* instance which initiated the kick event.
|
||||
* @param {?Function} _ - Used only in native code.
|
||||
* @returns {Function}
|
||||
* @param {string} [title] - The dialog title.
|
||||
*
|
||||
* @returns {Promise} Resolved when the dialog is closed.
|
||||
*/
|
||||
export function notifyKickedOut(participant: any, _?: Function) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!participant || participant?.isReplaced()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getId())
|
||||
};
|
||||
|
||||
dispatch(showNotification({
|
||||
appearance: NOTIFICATION_TYPE.ERROR,
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.kickMessage',
|
||||
descriptionArguments: args,
|
||||
titleKey: 'dialog.kickTitle',
|
||||
titleArguments: args
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
};
|
||||
export function openLeaveReasonDialog(title?: string) {
|
||||
return (dispatch: IStore['dispatch']): Promise<void> => new Promise(resolve => {
|
||||
dispatch(openDialog(LeaveReasonDialog, {
|
||||
onClose: resolve,
|
||||
title
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
dialog: {
|
||||
marginBottom: theme.spacing(1)
|
||||
},
|
||||
|
||||
text: {
|
||||
fontSize: '20px'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link LeaveReasonDialog}.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Callback invoked when {@code LeaveReasonDialog} is unmounted.
|
||||
*/
|
||||
onClose: () => void;
|
||||
|
||||
/**
|
||||
* The title to display in the dialog.
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React {@code Component} for displaying a dialog with a reason that ended the conference.
|
||||
*
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
const LeaveReasonDialog = ({ onClose, title }: IProps) => {
|
||||
const { classes } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => () => {
|
||||
onClose?.();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ hidden: true }}
|
||||
onSubmit = { onClose }
|
||||
size = 'medium'
|
||||
testId = 'dialog.leaveReason'>
|
||||
<div className = { classes.dialog }>
|
||||
{title ? <div className = { classes.text }>{t(title)}</div> : null}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeaveReasonDialog;
|
||||
@@ -9,10 +9,8 @@ import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
KICKED_OUT
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference/actionTypes';
|
||||
import { conferenceLeft } from '../base/conference/actions';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
@@ -41,7 +39,7 @@ import { showSalesforceNotification } from '../salesforce/actions';
|
||||
import { setToolboxEnabled } from '../toolbox/actions.any';
|
||||
|
||||
import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
|
||||
import { dismissCalendarNotification, notifyKickedOut } from './actions';
|
||||
import { dismissCalendarNotification } from './actions';
|
||||
import { IFRAME_DISABLED_TIMEOUT_MINUTES, IFRAME_EMBED_ALLOWED_LOCATIONS } from './constants';
|
||||
|
||||
|
||||
@@ -64,20 +62,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(notifyKickedOut(
|
||||
action.participant,
|
||||
() => {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DISMISS_CALENDAR_NOTIFICATION:
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_FAILED: {
|
||||
|
||||
@@ -1 +1,28 @@
|
||||
import { appNavigate } from '../app/actions.native';
|
||||
import { KICKED_OUT } from '../base/conference/actionTypes';
|
||||
import { conferenceLeft } from '../base/conference/actions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { notifyKickedOut } from './actions.native';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(notifyKickedOut(
|
||||
action.participant,
|
||||
() => {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { CONFERENCE_JOINED, KICKED_OUT } from '../base/conference/actionTypes';
|
||||
import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { hangup } from '../base/connection/actions.web';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web';
|
||||
import { CAMERA_FACING_MODE_MESSAGE } from '../base/tracks/constants';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(_store => next => action => {
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
_addSetCameraFacingModeListener(action.conference);
|
||||
break;
|
||||
}
|
||||
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
const { participant } = action;
|
||||
|
||||
const participantDisplayName
|
||||
= getParticipantDisplayName(store.getState, participant.getId());
|
||||
|
||||
dispatch(hangup(true, i18next.t('dialog.kickTitle', { participantDisplayName })));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -79,10 +79,8 @@ export function submitVideoDeviceSelectionTab(newState: any, isDisplayedOnWelcom
|
||||
userSelectedCameraDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
|
||||
}));
|
||||
|
||||
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
|
||||
}
|
||||
|
||||
if (newState.localFlipX !== currentState.localFlipX) {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: newState.localFlipX
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getDeviceIdByLabel,
|
||||
groupDevicesByKind
|
||||
} from '../base/devices/functions.web';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { isIosMobileBrowser, isMobileBrowser } from '../base/environment/utils';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import {
|
||||
@@ -97,7 +97,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
|
||||
export function getVideoDeviceSelectionDialogProps(stateful: IStateful, isDisplayedOnWelcomePage: boolean) {
|
||||
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
|
||||
// by the browser when a new track is created for preview. That's why we are disabling all previews.
|
||||
const disablePreviews = isIosMobileBrowser();
|
||||
const disablePreviews = isMobileBrowser();
|
||||
|
||||
const state = toState(stateful);
|
||||
const settings = state['features/base/settings'];
|
||||
|
||||
@@ -41,14 +41,16 @@ export function cancelFeedback(score: number, message: string) {
|
||||
* @param {JistiConference} conference - The conference for which the feedback
|
||||
* would be about. The conference is passed in because feedback can occur after
|
||||
* a conference has been left, so references to it may no longer exist in redux.
|
||||
* @param {string} title - The feedback dialog title.
|
||||
* @returns {Promise} Resolved with value - false if the dialog is enabled and
|
||||
* resolved with true if the dialog is disabled or the feedback was already
|
||||
* submitted. Rejected if another dialog is already displayed.
|
||||
*/
|
||||
export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: string) {
|
||||
type R = {
|
||||
feedbackSubmitted: boolean;
|
||||
showThankYou: boolean;
|
||||
wasDialogShown: boolean;
|
||||
};
|
||||
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<R> => {
|
||||
@@ -67,16 +69,18 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
|
||||
return Promise.resolve({
|
||||
feedbackSubmitted: true,
|
||||
showThankYou: true
|
||||
showThankYou: true,
|
||||
wasDialogShown: false
|
||||
});
|
||||
} else if (conference.isCallstatsEnabled() && feedbackPercentage > Math.random() * 100) {
|
||||
return new Promise(resolve => {
|
||||
dispatch(openFeedbackDialog(conference, () => {
|
||||
dispatch(openFeedbackDialog(conference, title, () => {
|
||||
const { submitted } = getState()['features/feedback'];
|
||||
|
||||
resolve({
|
||||
feedbackSubmitted: submitted,
|
||||
showThankYou: false
|
||||
showThankYou: false,
|
||||
wasDialogShown: true
|
||||
});
|
||||
}));
|
||||
});
|
||||
@@ -87,7 +91,8 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
// act on it.
|
||||
return Promise.resolve({
|
||||
feedbackSubmitted: false,
|
||||
showThankYou: true
|
||||
showThankYou: true,
|
||||
wasDialogShown: false
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -98,14 +103,16 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
* @param {JitsiConference} conference - The JitsiConference that is being
|
||||
* rated. The conference is passed in because feedback can occur after a
|
||||
* conference has been left, so references to it may no longer exist in redux.
|
||||
* @param {string} [title] - The feedback dialog title.
|
||||
* @param {Function} [onClose] - An optional callback to invoke when the dialog
|
||||
* is closed.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function openFeedbackDialog(conference?: IJitsiConference, onClose?: Function) {
|
||||
export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
|
||||
return openDialog(FeedbackDialog, {
|
||||
conference,
|
||||
onClose
|
||||
onClose,
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@ const useStyles = makeStyles()(theme => {
|
||||
}
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: '16px'
|
||||
},
|
||||
|
||||
details: {
|
||||
'& textarea': {
|
||||
minHeight: '122px'
|
||||
@@ -98,6 +102,11 @@ interface IProps {
|
||||
* Callback invoked when {@code FeedbackDialog} is unmounted.
|
||||
*/
|
||||
onClose: Function;
|
||||
|
||||
/**
|
||||
* The title to display in the dialog. Usually the reason that triggered the feedback.
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +117,7 @@ interface IProps {
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
const FeedbackDialog = ({ conference, onClose }: IProps) => {
|
||||
const FeedbackDialog = ({ conference, onClose, title }: IProps) => {
|
||||
const { classes } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
@@ -284,6 +293,7 @@ const FeedbackDialog = ({ conference, onClose }: IProps) => {
|
||||
size = 'large'
|
||||
titleKey = 'feedback.rateExperience'>
|
||||
<div className = { classes.dialog }>
|
||||
{title ? <div className = { classes.title }>{t(title)}</div> : null}
|
||||
<div className = { classes.rating }>
|
||||
<div
|
||||
className = { classes.stars }
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
isStageFilmstripTopPanel,
|
||||
shouldRemoteVideosBeVisible
|
||||
} from '../../functions';
|
||||
import { isFilmstripDisabled } from '../../functions.web';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
import Thumbnail from './Thumbnail';
|
||||
@@ -74,6 +75,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_disableSelfView: boolean;
|
||||
|
||||
/**
|
||||
* Whether vertical/horizontal filmstrip is disabled through config.
|
||||
*/
|
||||
_filmstripDisabled: boolean;
|
||||
|
||||
/**
|
||||
* The height of the filmstrip.
|
||||
*/
|
||||
@@ -333,6 +339,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
const {
|
||||
_currentLayout,
|
||||
_disableSelfView,
|
||||
_filmstripDisabled,
|
||||
_localScreenShareId,
|
||||
_mainFilmstripVisible,
|
||||
_resizableFilmstrip,
|
||||
@@ -381,7 +388,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
let toolbar = null;
|
||||
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW
|
||||
&& ((filmstripType === FILMSTRIP_TYPE.MAIN && !_filmstripDisabled)
|
||||
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
|
||||
toolbar = this._renderToggleButton();
|
||||
}
|
||||
@@ -885,6 +893,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { isOpen: shiftRight } = state['features/chat'];
|
||||
const disableSelfView = getHideSelfView(state);
|
||||
const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
|
||||
const filmstripDisabled = isFilmstripDisabled(state);
|
||||
|
||||
const collapseTileView = reduceHeight
|
||||
&& isMobileBrowser()
|
||||
@@ -893,7 +902,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const shouldReduceHeight = reduceHeight && isMobileBrowser();
|
||||
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
|
||||
|
||||
let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
const notDisabled = visible && !filmstripDisabled;
|
||||
let isVisible = notDisabled || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
|
||||
if (_topPanelFilmstrip) {
|
||||
isVisible = _topPanelVisible;
|
||||
@@ -912,13 +922,14 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
_currentLayout,
|
||||
_disableSelfView: disableSelfView,
|
||||
_filmstripDisabled: filmstripDisabled,
|
||||
_hasScroll,
|
||||
_iAmRecorder: Boolean(iAmRecorder),
|
||||
_isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
|
||||
_isToolboxVisible: isToolboxVisible(state),
|
||||
_isVerticalFilmstrip,
|
||||
_localScreenShareId: localScreenShare?.id,
|
||||
_mainFilmstripVisible: visible,
|
||||
_mainFilmstripVisible: notDisabled,
|
||||
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
|
||||
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
|
||||
_remoteParticipantsLength: _remoteParticipants?.length ?? 0,
|
||||
|
||||
@@ -763,6 +763,18 @@ export function isStageFilmstripEnabled(state: IReduxState) {
|
||||
return Boolean(!filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the vertical/horizontal filmstrip is disabled.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isFilmstripDisabled(state: IReduxState) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
|
||||
return Boolean(filmstrip?.disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thumbnail type by filmstrip type.
|
||||
*
|
||||
|
||||
@@ -120,7 +120,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case SETTINGS_UPDATED: {
|
||||
if (typeof action.settings?.localFlipX === 'boolean') {
|
||||
// TODO: This needs to be removed once the large video is Reactified.
|
||||
VideoLayout.onLocalFlipXChanged();
|
||||
VideoLayout.onLocalFlipXChanged(action.settings.localFlipX);
|
||||
}
|
||||
if (action.settings?.disableSelfView) {
|
||||
const state = store.getState();
|
||||
|
||||
@@ -116,6 +116,6 @@ export default {
|
||||
},
|
||||
|
||||
sendBtn: {
|
||||
marginRight: BaseTheme.spacing[2]
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -445,7 +445,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
const { disableLobbyPassword } = getSecurityUiConfig(state);
|
||||
const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
|
||||
const deviceStatusVisible = isDeviceStatusVisible(state);
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
const { isLobbyChatActive, lobbyMessageRecipient, messages } = state['features/chat'];
|
||||
|
||||
return {
|
||||
@@ -461,7 +461,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
_participantId: participantId,
|
||||
_participantName: localParticipant?.name,
|
||||
_passwordJoinFailed: passwordJoinFailed,
|
||||
_renderPassword: !iAmSipGateway && !disableLobbyPassword,
|
||||
_renderPassword: !iAmSipGateway && !disableLobbyPassword && !lobbyWaitingForHost,
|
||||
showCopyUrlButton
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
error = { _passwordJoinFailed }
|
||||
onChange = { this._onChangePassword }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
placeholder = { t('lobby.enterPasswordButton') }
|
||||
secureTextEntry = { true }
|
||||
value = { this.state.password } />
|
||||
);
|
||||
|
||||
@@ -189,7 +189,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
className = { `lobby-prejoin-input ${_passwordJoinFailed ? 'error' : ''}` }
|
||||
id = 'lobby-password-input'
|
||||
onChange = { this._onChangePassword }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
placeholder = { t('lobby.enterPasswordButton') }
|
||||
testId = 'lobby.password'
|
||||
type = 'password'
|
||||
value = { this.state.password } />
|
||||
|
||||
22
react/features/mobile/full-screen/functions.ts
Normal file
22
react/features/mobile/full-screen/functions.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { isLocalVideoTrackDesktop } from '../../base/tracks/functions.any';
|
||||
|
||||
/**
|
||||
* Checks whether full-screen state should be used or not.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - Whether full-screen state shuld be used or not.
|
||||
*/
|
||||
export function shouldUseFullScreen(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
const isDesktopSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
return conference ? !audioOnly && !dialogOpen && !isDesktopSharing && fullscreenEnabled : false;
|
||||
}
|
||||
@@ -2,14 +2,11 @@ import ImmersiveMode from 'react-native-immersive-mode';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
|
||||
import { _setImmersiveSubscription } from './actions';
|
||||
import { shouldUseFullScreen } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
type BarVisibilityType = {
|
||||
@@ -48,14 +45,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
});
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
|
||||
return conference ? !audioOnly && !dialogOpen && fullscreenEnabled : false;
|
||||
},
|
||||
/* selector */ shouldUseFullScreen,
|
||||
/* listener */ fullScreen => _setFullScreen(fullScreen)
|
||||
);
|
||||
|
||||
@@ -73,13 +63,7 @@ function _onImmersiveChange({ getState }: IStore) {
|
||||
const { appState } = state['features/background'];
|
||||
|
||||
if (appState === 'active') {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
const fullScreen = conference ? !audioOnly && !dialogOpen && fullscreenEnabled : false;
|
||||
|
||||
_setFullScreen(fullScreen);
|
||||
_setFullScreen(shouldUseFullScreen(state));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ const HeaderNavigationButton = ({ color, disabled, label, onPress, src, style, t
|
||||
size = { 24 }
|
||||
src = { src }
|
||||
style = { [
|
||||
navigationStyles.headerNavigationButton,
|
||||
navigationStyles.headerNavigationButtonIcon,
|
||||
style
|
||||
] } />
|
||||
) : (
|
||||
@@ -87,8 +87,7 @@ const HeaderNavigationButton = ({ color, disabled, label, onPress, src, style, t
|
||||
btnStyle,
|
||||
style
|
||||
] }
|
||||
type = { BUTTON_TYPES.TERTIARY }
|
||||
useRippleColor = { false } />
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,12 @@ const headerNavigationButtonLabel = {
|
||||
lineHeight: BaseTheme.spacing[3]
|
||||
};
|
||||
|
||||
const headerNavigationButton = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BaseTheme.spacing[6],
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
};
|
||||
|
||||
/**
|
||||
* Styles of the navigation feature.
|
||||
*/
|
||||
@@ -43,7 +49,12 @@ export const navigationStyles = {
|
||||
},
|
||||
|
||||
headerNavigationButton: {
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
...headerNavigationButton
|
||||
},
|
||||
|
||||
headerNavigationButtonIcon: {
|
||||
...headerNavigationButton,
|
||||
padding: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
headerNavigationButtonDisabled: {
|
||||
|
||||
@@ -20,10 +20,10 @@ export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomNam
|
||||
const okDisabled = !roomName;
|
||||
const dispatch = useDispatch();
|
||||
const onBreakoutRoomNameChange = useCallback((newRoomName: string) => {
|
||||
setRoomName(newRoomName?.trim());
|
||||
setRoomName(newRoomName);
|
||||
}, [ setRoomName ]);
|
||||
const onSubmit = useCallback(() => {
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName));
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName?.trim()));
|
||||
}, [ breakoutRoomJid, dispatch, roomName ]);
|
||||
|
||||
return (<Dialog
|
||||
|
||||
@@ -85,7 +85,8 @@ export default {
|
||||
position: 'absolute',
|
||||
right: -3,
|
||||
top: -3,
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 2
|
||||
},
|
||||
|
||||
participantsButtonBadge: {
|
||||
|
||||
@@ -30,6 +30,33 @@ import MeetingParticipants from './MeetingParticipants';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
participantsPane: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width .16s ease-in-out',
|
||||
width: '315px',
|
||||
zIndex: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
fontWeight: 600,
|
||||
height: '100%',
|
||||
|
||||
[[ '& > *:first-child', '& > *:last-child' ] as any]: {
|
||||
flexShrink: 0
|
||||
},
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
|
||||
container: {
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
@@ -87,7 +114,7 @@ const useStyles = makeStyles()(theme => {
|
||||
});
|
||||
|
||||
const ParticipantsPane = () => {
|
||||
const { classes } = useStyles();
|
||||
const { classes, cx } = useStyles();
|
||||
const paneOpen = useSelector(getParticipantsPaneOpen);
|
||||
const isBreakoutRoomsSupported = useSelector((state: IReduxState) => state['features/base/conference'])
|
||||
.conference?.getBreakoutRooms()?.isSupported();
|
||||
@@ -136,49 +163,47 @@ const ParticipantsPane = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'participants_pane'>
|
||||
<div className = 'participants_pane-content'>
|
||||
<div className = { classes.header }>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('participantsPane.close', 'Close') }
|
||||
icon = { IconCloseLarge }
|
||||
onClick = { onClosePane } />
|
||||
</div>
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
{showMuteAllButton && (
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
labelKey = { 'participantsPane.actions.muteAll' }
|
||||
onClick = { onMuteAll }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
{showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
icon = { IconDotsHorizontal }
|
||||
id = 'participants-pane-context-menu'
|
||||
onClick = { onToggleContext }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className = { cx('participants_pane', classes.participantsPane) }>
|
||||
<div className = { classes.header }>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('participantsPane.close', 'Close') }
|
||||
icon = { IconCloseLarge }
|
||||
onClick = { onClosePane } />
|
||||
</div>
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
{showMuteAllButton && (
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
labelKey = { 'participantsPane.actions.muteAll' }
|
||||
onClick = { onMuteAll }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
{showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
icon = { IconDotsHorizontal }
|
||||
id = 'participants-pane-context-menu'
|
||||
onClick = { onToggleContext }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
BackHandler,
|
||||
Platform,
|
||||
StyleProp,
|
||||
Text,
|
||||
TextStyle,
|
||||
@@ -19,7 +18,6 @@ import { getConferenceName } from '../../../base/conference/functions';
|
||||
import { connect } from '../../../base/connection/actions.native';
|
||||
import { PREJOIN_PAGE_HIDE_DISPLAY_NAME } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import { getFieldValue } from '../../../base/react/functions';
|
||||
@@ -30,7 +28,7 @@ import Input from '../../../base/ui/components/native/Input';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import BrandingImageBackground from '../../../dynamic-branding/components/native/BrandingImageBackground';
|
||||
import LargeVideo from '../../../large-video/components/LargeVideo.native';
|
||||
import HeaderNavigationButton from '../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { screenHeaderCloseButton } from '../../../mobile/navigation/functions';
|
||||
import { navigateRoot } from '../../../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import AudioMuteButton from '../../../toolbox/components/native/AudioMuteButton';
|
||||
@@ -40,6 +38,7 @@ import { IPrejoinProps } from '../../types';
|
||||
|
||||
import { preJoinStyles as styles } from './styles';
|
||||
|
||||
|
||||
const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const isFocused = useIsFocused();
|
||||
@@ -83,22 +82,6 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
return true;
|
||||
}, [ dispatch ]);
|
||||
|
||||
const headerLeft = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
label = { t('dialog.close') }
|
||||
onPress = { goBack } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
onPress = { goBack }
|
||||
src = { IconCloseLarge } />
|
||||
);
|
||||
};
|
||||
|
||||
const { PRIMARY, TERTIARY } = BUTTON_TYPES;
|
||||
const joinButtonDisabled = !displayName && isDisplayNameMandatory;
|
||||
|
||||
@@ -111,7 +94,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft,
|
||||
headerLeft: () => screenHeaderCloseButton(goBack),
|
||||
headerTitle: t('prejoin.joinMeeting')
|
||||
});
|
||||
}, [ navigation ]);
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import React, { useCallback, useLayoutEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Platform, StyleProp, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { StyleProp, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../../app/actions.native';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getConferenceName } from '../../../base/conference/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge, IconWarning } from '../../../base/icons/svg';
|
||||
import { IconWarning } from '../../../base/icons/svg';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import getUnsafeRoomText from '../../../base/util/getUnsafeRoomText.native';
|
||||
import HeaderNavigationButton from '../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { screenHeaderCloseButton } from '../../../mobile/navigation/functions';
|
||||
import { navigateRoot } from '../../../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { IPrejoinProps } from '../../types';
|
||||
|
||||
import { preJoinStyles as styles } from './styles';
|
||||
|
||||
|
||||
const UnsafeRoomWarning: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
@@ -41,25 +42,9 @@ const UnsafeRoomWarning: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProp
|
||||
return true;
|
||||
}, [ dispatch ]);
|
||||
|
||||
const headerLeft = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
label = { t('dialog.close') }
|
||||
onPress = { goBack } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
onPress = { goBack }
|
||||
src = { IconCloseLarge } />
|
||||
);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft,
|
||||
headerLeft: () => screenHeaderCloseButton(goBack),
|
||||
headerTitle: t('prejoin.joinMeeting')
|
||||
});
|
||||
}, [ navigation ]);
|
||||
|
||||
@@ -476,7 +476,11 @@ export function endpointMessageReceived(participantId: string, message: {
|
||||
if (type === EVENTS.stop) {
|
||||
dispatch(stopReceiver(false, true));
|
||||
} else { // forward the message
|
||||
transport?.sendEvent(message);
|
||||
try {
|
||||
transport?.sendEvent(message);
|
||||
} catch (error) {
|
||||
logger.error('Error while trying to execute remote control message', error);
|
||||
}
|
||||
}
|
||||
} // else ignore
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { hangup } from '../base/connection/actions.web';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import browser from '../base/lib-jitsi-meet';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
import { getLocalVideoTrack } from '../base/tracks/functions.web';
|
||||
import { appendURLHashParam } from '../base/util/uri';
|
||||
@@ -65,6 +65,8 @@ export function openLogoutDialog() {
|
||||
} else {
|
||||
if (logoutUrl) {
|
||||
window.location.href = logoutUrl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference?.room.moderator.logout(() => dispatch(hangup(true)));
|
||||
|
||||
@@ -64,17 +64,17 @@ const GeneralSection = () => {
|
||||
</FormRow>}
|
||||
|
||||
<FormRow label = 'settings.language'>
|
||||
<TouchableHighlight onPress = { navigateToLanguageSelect }>
|
||||
<View style = { styles.languageButton as ViewStyle }>
|
||||
<Text
|
||||
style = { styles.languageText }>{t(`languages:${language}`)}</Text>
|
||||
<View>
|
||||
<View style = { styles.languageButtonContainer as ViewStyle }>
|
||||
<TouchableHighlight onPress = { navigateToLanguageSelect }>
|
||||
<View style = { styles.languageButton as ViewStyle }>
|
||||
<Text
|
||||
style = { styles.languageText }>{t(`languages:${language}`)}</Text>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconArrowRight } />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
</FormRow>
|
||||
</FormSection>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import React, { useCallback, useLayoutEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, Text, TouchableHighlight, View, ViewStyle } from 'react-native';
|
||||
|
||||
import i18next, { DEFAULT_LANGUAGE, LANGUAGES } from '../../../base/i18n/i18next';
|
||||
import { IconArrowLeft } from '../../../base/icons/svg';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { navigate } from '../../../mobile/navigation/components/settings/SettingsNavigationContainerRef';
|
||||
import BaseThemeNative from '../../../base/ui/components/BaseTheme.native';
|
||||
import HeaderNavigationButton from '../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { goBack, navigate } from '../../../mobile/navigation/components/settings/SettingsNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
const LanguageSelectView = ({ isInWelcomePage }: { isInWelcomePage?: boolean; }) => {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation();
|
||||
const { language: currentLanguage = DEFAULT_LANGUAGE } = i18next;
|
||||
|
||||
const setLanguage = useCallback(language => () => {
|
||||
@@ -18,6 +23,21 @@ const LanguageSelectView = ({ isInWelcomePage }: { isInWelcomePage?: boolean; })
|
||||
navigate(screen.settings.main);
|
||||
}, [ i18next ]);
|
||||
|
||||
const headerLeft = () => (
|
||||
<HeaderNavigationButton
|
||||
color = { BaseThemeNative.palette.link01 }
|
||||
onPress = { goBack }
|
||||
src = { IconArrowLeft }
|
||||
style = { styles.backBtn }
|
||||
twoActions = { true } />
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft
|
||||
});
|
||||
}, [ navigation ]);
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
disableForcedKeyboardDismiss = { true }
|
||||
|
||||
@@ -73,8 +73,9 @@ export default {
|
||||
fieldContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
minHeight: 56,
|
||||
paddingHorizontal: 8
|
||||
minHeight: BaseTheme.spacing[8],
|
||||
paddingHorizontal: BaseTheme.spacing[2],
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -90,9 +91,10 @@ export default {
|
||||
*/
|
||||
fieldLabelContainer: {
|
||||
alignItems: 'center',
|
||||
flexShrink: 1,
|
||||
flexDirection: 'row',
|
||||
flex: 3,
|
||||
paddingLeft: BaseTheme.spacing[3]
|
||||
paddingLeft: BaseTheme.spacing[3],
|
||||
paddingRight: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -118,8 +120,8 @@ export default {
|
||||
*/
|
||||
fieldValueContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexShrink: 1,
|
||||
justifyContent: 'flex-end',
|
||||
paddingRight: BaseTheme.spacing[3]
|
||||
},
|
||||
@@ -156,9 +158,17 @@ export default {
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
languageButtonContainer: {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
|
||||
languageButton: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[7],
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
languageOption: {
|
||||
@@ -178,7 +188,7 @@ export default {
|
||||
languageText: {
|
||||
...BaseTheme.typography.bodyShortRegularLarge,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
marginHorizontal: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -225,10 +235,10 @@ export default {
|
||||
},
|
||||
|
||||
logBtn: {
|
||||
marginRight: BaseTheme.spacing[2]
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
backBtn: {
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -212,15 +212,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
cancel: () => {
|
||||
const { options } = getVirtualBackgroundTabProps(state, isDisplayedOnWelcomePage);
|
||||
|
||||
return submitVirtualBackgroundTab({
|
||||
options: {
|
||||
backgroundType: options.backgroundType,
|
||||
enabled: options.backgroundEffectEnabled,
|
||||
url: options.virtualSource,
|
||||
selectedThumbnail: options.selectedThumbnail,
|
||||
blurValue: options.blurValue
|
||||
}
|
||||
}, true);
|
||||
return submitVirtualBackgroundTab({ options }, true);
|
||||
},
|
||||
icon: IconImage
|
||||
});
|
||||
|
||||
@@ -34,7 +34,6 @@ function HangupMenu() {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createToolbarEvent('endmeeting'));
|
||||
dispatch(endConference());
|
||||
dispatch(appNavigate(undefined));
|
||||
}, [ hideSheet ]);
|
||||
|
||||
const handleLeaveConference = useCallback(() => {
|
||||
|
||||
@@ -36,10 +36,50 @@ interface IProps {
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
drawerMenuContainer: {
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
|
||||
drawer: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
maxHeight: `calc(${DRAWER_MAX_HEIGHT})`,
|
||||
borderRadius: '24px 24px 0 0'
|
||||
borderRadius: '24px 24px 0 0',
|
||||
overflowY: 'auto',
|
||||
marginBottom: 'env(safe-area-inset-bottom, 0)',
|
||||
width: '100%',
|
||||
|
||||
'& .overflow-menu': {
|
||||
margin: 'auto',
|
||||
fontSize: '1.2em',
|
||||
listStyleType: 'none',
|
||||
padding: 0,
|
||||
height: 'calc(80vh - 144px - 64px)',
|
||||
overflowY: 'auto',
|
||||
|
||||
'& .overflow-menu-item': {
|
||||
boxSizing: 'border-box',
|
||||
height: '48px',
|
||||
padding: '12px 16px',
|
||||
alignItems: 'center',
|
||||
color: theme.palette.text01,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
fontSize: '16px',
|
||||
|
||||
'& div': {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
|
||||
'&.disabled': {
|
||||
cursor: 'initial',
|
||||
color: '#3b475c'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -56,7 +96,7 @@ function Drawer({
|
||||
isOpen,
|
||||
onClose
|
||||
}: IProps) {
|
||||
const { classes: styles } = useStyles();
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
/**
|
||||
* Handles clicks within the menu, preventing the propagation of the click event.
|
||||
@@ -96,11 +136,11 @@ function Drawer({
|
||||
return (
|
||||
isOpen ? (
|
||||
<div
|
||||
className = 'drawer-menu-container'
|
||||
className = { classes.drawerMenuContainer }
|
||||
onClick = { handleOutsideClick }
|
||||
onKeyDown = { handleEscKey }>
|
||||
<div
|
||||
className = { `drawer-menu ${styles.drawer} ${className}` }
|
||||
className = { cx(classes.drawer, className) }
|
||||
onClick = { handleInsideClick }>
|
||||
<FocusOn
|
||||
returnFocus = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import DialogPortal from './DialogPortal';
|
||||
|
||||
@@ -15,6 +16,29 @@ interface IProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
portal: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 351,
|
||||
borderRadius: '16px 16px 0 0',
|
||||
|
||||
'&.notification-portal': {
|
||||
zIndex: 901
|
||||
},
|
||||
|
||||
'&::after': {
|
||||
content: '',
|
||||
backgroundColor: theme.palette.ui01,
|
||||
marginBottom: 'env(safe-area-inset-bottom, 0)'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component meant to render a drawer at the bottom of the screen,
|
||||
* by creating a portal containing the component's children.
|
||||
@@ -22,8 +46,10 @@ interface IProps {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function JitsiPortal({ children, className }: IProps) {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
return (
|
||||
<DialogPortal className = { `drawer-portal ${className ?? ''}` }>
|
||||
<DialogPortal className = { cx(classes.portal, className) }>
|
||||
{ children }
|
||||
</DialogPortal>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
getParticipantDisplayName,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants/functions';
|
||||
import { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
|
||||
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { IRoom } from '../../../breakout-rooms/types';
|
||||
import PrivateMessageButton from '../../../chat/components/native/PrivateMessageButton';
|
||||
|
||||
@@ -67,6 +67,11 @@ interface IProps {
|
||||
*/
|
||||
_disableRemoteMute: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the current room is a breakout room.
|
||||
*/
|
||||
_isBreakoutRoom: boolean;
|
||||
|
||||
/**
|
||||
* Whether the participant is present in the room or not.
|
||||
*/
|
||||
@@ -130,6 +135,7 @@ class RemoteVideoMenu extends PureComponent<IProps> {
|
||||
_disablePrivateChat,
|
||||
_disableRemoteMute,
|
||||
_disableGrantModerator,
|
||||
_isBreakoutRoom,
|
||||
_isParticipantAvailable,
|
||||
_moderator,
|
||||
_rooms,
|
||||
@@ -160,7 +166,7 @@ class RemoteVideoMenu extends PureComponent<IProps> {
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
{ !_disableKick && <KickButton { ...buttonProps } /> }
|
||||
{ !_disableGrantModerator && <GrantModeratorButton { ...buttonProps } /> }
|
||||
{ !_disableGrantModerator && !_isBreakoutRoom && <GrantModeratorButton { ...buttonProps } /> }
|
||||
<PinButton { ...buttonProps } />
|
||||
{ !_disablePrivateChat && <PrivateMessageButton { ...buttonProps } /> }
|
||||
<ConnectionStatusButton { ...connectionStatusButtonProps } />
|
||||
@@ -235,12 +241,14 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const shouldDisableKick = disableKick || !kickOutEnabled;
|
||||
const moderator = isLocalParticipantModerator(state);
|
||||
const _iAmVisitor = state['features/visitors'].iAmVisitor;
|
||||
const _isBreakoutRoom = isInBreakoutRoom(state);
|
||||
|
||||
return {
|
||||
_currentRoomId,
|
||||
_disableKick: Boolean(shouldDisableKick),
|
||||
_disableRemoteMute: Boolean(disableRemoteMute),
|
||||
_disablePrivateChat: Boolean(disablePrivateChat) || _iAmVisitor,
|
||||
_isBreakoutRoom,
|
||||
_isParticipantAvailable: Boolean(isParticipantAvailable),
|
||||
_moderator: moderator,
|
||||
_participantDisplayName: getParticipantDisplayName(state, participantId),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createVirtualBackgroundEffect } from '../stream-effects/virtual-backgro
|
||||
|
||||
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
|
||||
import logger from './logger';
|
||||
import { IVirtualBackgroundOptions } from './types';
|
||||
import { IVirtualBackground } from './reducer';
|
||||
|
||||
/**
|
||||
* Signals the local participant activate the virtual background video or not.
|
||||
@@ -12,16 +12,16 @@ import { IVirtualBackgroundOptions } from './types';
|
||||
* @param {Object} jitsiTrack - Represents the jitsi track that will have backgraund effect applied.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function toggleBackgroundEffect(options: IVirtualBackgroundOptions, jitsiTrack: any) {
|
||||
export function toggleBackgroundEffect(options: IVirtualBackground, jitsiTrack: any) {
|
||||
return async function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
await dispatch(backgroundEnabled(options.enabled));
|
||||
await dispatch(backgroundEnabled(options.backgroundEffectEnabled));
|
||||
await dispatch(setVirtualBackground(options));
|
||||
const state = getState();
|
||||
const virtualBackground = state['features/virtual-background'];
|
||||
|
||||
if (jitsiTrack) {
|
||||
try {
|
||||
if (options.enabled) {
|
||||
if (options.backgroundEffectEnabled) {
|
||||
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground, dispatch));
|
||||
} else {
|
||||
await jitsiTrack.setEffect(undefined);
|
||||
@@ -46,10 +46,10 @@ export function toggleBackgroundEffect(options: IVirtualBackgroundOptions, jitsi
|
||||
* type: string,
|
||||
* }}
|
||||
*/
|
||||
export function setVirtualBackground(options?: IVirtualBackgroundOptions) {
|
||||
export function setVirtualBackground(options?: IVirtualBackground) {
|
||||
return {
|
||||
type: SET_VIRTUAL_BACKGROUND,
|
||||
virtualSource: options?.url,
|
||||
virtualSource: options?.virtualSource,
|
||||
blurValue: options?.blurValue,
|
||||
backgroundType: options?.backgroundType,
|
||||
selectedThumbnail: options?.selectedThumbnail
|
||||
@@ -65,7 +65,7 @@ export function setVirtualBackground(options?: IVirtualBackgroundOptions) {
|
||||
* backgroundEffectEnabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function backgroundEnabled(backgroundEffectEnabled: boolean) {
|
||||
export function backgroundEnabled(backgroundEffectEnabled?: boolean) {
|
||||
return {
|
||||
type: BACKGROUND_ENABLED,
|
||||
backgroundEffectEnabled
|
||||
|
||||
@@ -105,10 +105,10 @@ function UploadImageButton({
|
||||
}
|
||||
]);
|
||||
setOptions({
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: uuId
|
||||
selectedThumbnail: uuId,
|
||||
virtualSource: url
|
||||
});
|
||||
};
|
||||
logger.info('New virtual background image uploaded!');
|
||||
|
||||
@@ -15,6 +15,7 @@ import { showWarningNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { toggleBackgroundEffect } from '../actions';
|
||||
import logger from '../logger';
|
||||
import { IVirtualBackground } from '../reducer';
|
||||
|
||||
/**
|
||||
* The type of the React {@code PureComponent} props of {@link VirtualBackgroundPreview}.
|
||||
@@ -39,7 +40,7 @@ export interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Represents the virtual background set options.
|
||||
*/
|
||||
options: any;
|
||||
options: IVirtualBackground;
|
||||
|
||||
/**
|
||||
* The id of the selected video device.
|
||||
|
||||
@@ -19,6 +19,7 @@ import Spinner from '../../base/ui/components/web/Spinner';
|
||||
import { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
||||
import { toDataURL } from '../functions';
|
||||
import logger from '../logger';
|
||||
import { IVirtualBackground } from '../reducer';
|
||||
|
||||
import UploadImageButton from './UploadImageButton';
|
||||
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
||||
@@ -72,7 +73,7 @@ interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Virtual background options.
|
||||
*/
|
||||
options: any;
|
||||
options: IVirtualBackground;
|
||||
|
||||
/**
|
||||
* Returns the selected thumbnail identifier.
|
||||
@@ -254,8 +255,8 @@ function VirtualBackgrounds({
|
||||
|
||||
const enableBlur = useCallback(async () => {
|
||||
onOptionsChange({
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
||||
enabled: true,
|
||||
blurValue: 25,
|
||||
selectedThumbnail: 'blur'
|
||||
});
|
||||
@@ -272,8 +273,8 @@ function VirtualBackgrounds({
|
||||
|
||||
const enableSlideBlur = useCallback(async () => {
|
||||
onOptionsChange({
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
||||
enabled: true,
|
||||
blurValue: 8,
|
||||
selectedThumbnail: 'slight-blur'
|
||||
});
|
||||
@@ -290,7 +291,7 @@ function VirtualBackgrounds({
|
||||
|
||||
const removeBackground = useCallback(async () => {
|
||||
onOptionsChange({
|
||||
enabled: false,
|
||||
backgroundEffectEnabled: false,
|
||||
selectedThumbnail: 'none'
|
||||
});
|
||||
logger.info('"None" option set for virtual background preview!');
|
||||
@@ -310,10 +311,10 @@ function VirtualBackgrounds({
|
||||
|
||||
if (image) {
|
||||
onOptionsChange({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url: image.src,
|
||||
selectedThumbnail: image.id
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
selectedThumbnail: image.id,
|
||||
virtualSource: image.src
|
||||
});
|
||||
logger.info('Uploaded image set for virtual background preview!');
|
||||
}
|
||||
@@ -328,10 +329,10 @@ function VirtualBackgrounds({
|
||||
const url = await toDataURL(image.src);
|
||||
|
||||
onOptionsChange({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: image.id
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
selectedThumbnail: image.id,
|
||||
virtualSource: url
|
||||
});
|
||||
logger.info('Image set for virtual background preview!');
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { IVirtualBackground } from './reducer';
|
||||
|
||||
export interface IVirtualBackgroundOptions extends IVirtualBackground {
|
||||
enabled: boolean;
|
||||
url?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user