mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-11 04:42:30 +00:00
Compare commits
40 Commits
android-sd
...
3733
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2544d0a084 | ||
|
|
8f0a12016a | ||
|
|
34ccd3524f | ||
|
|
563e99ecd3 | ||
|
|
70f14be50f | ||
|
|
8bd0da886e | ||
|
|
1fd326f980 | ||
|
|
d9cc664ea6 | ||
|
|
d65e241056 | ||
|
|
fe2b1f3d9f | ||
|
|
17c1f50fc3 | ||
|
|
5c1c022291 | ||
|
|
72435dee56 | ||
|
|
42f2eff02a | ||
|
|
0b68bef0be | ||
|
|
676e943d81 | ||
|
|
2b4307dee9 | ||
|
|
f3f936c196 | ||
|
|
eb900ddbe1 | ||
|
|
c2c323347a | ||
|
|
af6642b91b | ||
|
|
ffded8d82a | ||
|
|
00b57c7983 | ||
|
|
5d40a8992a | ||
|
|
e543625295 | ||
|
|
0b25ff649e | ||
|
|
63344ac62d | ||
|
|
2e60aafebf | ||
|
|
131e8f4aea | ||
|
|
53f01a39c9 | ||
|
|
50f4796144 | ||
|
|
5bdfae377f | ||
|
|
44970648ea | ||
|
|
3cd7f0b77d | ||
|
|
4d243f9b92 | ||
|
|
6b716f8f56 | ||
|
|
5b99219f29 | ||
|
|
f0dcb51915 | ||
|
|
3ff658a13b | ||
|
|
3a46513d4b |
@@ -27,10 +27,18 @@ You can download Debian/Ubuntu binaries:
|
||||
You can download source archives (produced by ```make source-package```):
|
||||
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
|
||||
|
||||
### Mobile apps
|
||||
|
||||
You can get our mobile versions from here:
|
||||
|
||||
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
You can also sign up for our open beta testing here:
|
||||
|
||||
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)
|
||||
* [iOS](https://testflight.apple.com/join/isy6ja7S)
|
||||
|
||||
## Development
|
||||
|
||||
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).
|
||||
|
||||
@@ -24,7 +24,7 @@ platform :android do
|
||||
|
||||
# Upload built artifact to the Closed Beta track
|
||||
upload_to_play_store(
|
||||
track: "Closed Beta",
|
||||
track: "beta",
|
||||
json_key: ENV["JITSI_JSON_KEY_FILE"],
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=19.4.0
|
||||
sdkVersion=2.4.0
|
||||
appVersion=19.5.0
|
||||
sdkVersion=2.5.0
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.telecom.CallAudioState;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -52,20 +53,20 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private static int audioDeviceToRouteInt(String audioDevice) {
|
||||
if (audioDevice == null) {
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
switch (audioDevice) {
|
||||
case AudioModeModule.DEVICE_BLUETOOTH:
|
||||
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
|
||||
return CallAudioState.ROUTE_BLUETOOTH;
|
||||
case AudioModeModule.DEVICE_EARPIECE:
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
case AudioModeModule.DEVICE_HEADPHONES:
|
||||
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
return CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
case AudioModeModule.DEVICE_SPEAKER:
|
||||
return android.telecom.CallAudioState.ROUTE_SPEAKER;
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
default:
|
||||
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,20 +79,16 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
|
||||
Set<String> devices = new HashSet<>();
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
|
||||
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE) == CallAudioState.ROUTE_EARPIECE) {
|
||||
devices.add(AudioModeModule.DEVICE_EARPIECE);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
|
||||
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH) == CallAudioState.ROUTE_BLUETOOTH) {
|
||||
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
|
||||
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER) == CallAudioState.ROUTE_SPEAKER) {
|
||||
devices.add(AudioModeModule.DEVICE_SPEAKER);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
|
||||
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) == CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
}
|
||||
return devices;
|
||||
@@ -109,13 +106,13 @@ class AudioDeviceHandlerConnectionService implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAudioStateChange(final android.telecom.CallAudioState callAudioState) {
|
||||
public void onCallAudioStateChange(final CallAudioState state) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean audioRouteChanged
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != callAudioState.getRoute();
|
||||
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != state.getRoute();
|
||||
int newSupportedRoutes = state.getSupportedRouteMask();
|
||||
boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
|
||||
if (audioDevicesChanged) {
|
||||
supportedRouteMask = newSupportedRoutes;
|
||||
@@ -140,6 +137,8 @@ class AudioDeviceHandlerConnectionService implements
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(this);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +147,8 @@ class AudioDeviceHandlerConnectionService implements
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(null);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,11 +198,11 @@ class AudioDeviceHandlerGeneric implements
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -196,11 +196,11 @@ class AudioDeviceHandlerLegacy implements
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -136,8 +136,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
public AudioModeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,6 +191,16 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the audio device handler module. This function is called *after* all Catalyst
|
||||
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
|
||||
* needs access to another Catalyst module, so doing this in the constructor would be too early.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
|
||||
private void setAudioDeviceHandler() {
|
||||
if (audioDeviceHandler != null) {
|
||||
audioDeviceHandler.stop();
|
||||
|
||||
@@ -349,7 +349,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (token == null && userInfo != null) {
|
||||
if (userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
|
||||
@@ -1205,12 +1205,16 @@ export default {
|
||||
|
||||
_getConferenceOptions() {
|
||||
const options = config;
|
||||
const { email, name: nick } = getLocalParticipant(APP.store.getState());
|
||||
|
||||
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||
const { locationURL } = APP.store.getState()['features/base/connection'];
|
||||
|
||||
if (nick) {
|
||||
options.displayName = nick;
|
||||
if (options.enableDisplayNameInStats && nick) {
|
||||
options.statisticsDisplayName = nick;
|
||||
}
|
||||
|
||||
if (options.enableEmailInStats && email) {
|
||||
options.statisticsId = email;
|
||||
}
|
||||
|
||||
options.applicationName = interfaceConfig.APP_NAME;
|
||||
|
||||
@@ -292,13 +292,11 @@ var config = {
|
||||
// callStatsID: '',
|
||||
// callStatsSecret: '',
|
||||
|
||||
// enables callstatsUsername to be reported as statsId and used
|
||||
// by callstats as repoted remote id
|
||||
// enableStatsID: false
|
||||
|
||||
// enables sending participants display name to callstats
|
||||
// enableDisplayNameInStats: false
|
||||
|
||||
// enables sending participants email if available to callstats and other analytics
|
||||
// enableEmailInStats: false
|
||||
|
||||
// Privacy
|
||||
//
|
||||
|
||||
@@ -205,6 +205,10 @@
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -226,6 +230,8 @@
|
||||
|
||||
.messagecontent {
|
||||
margin: 5px 10px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -489,6 +489,8 @@
|
||||
height: 300px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#mixedstream {
|
||||
|
||||
14
doc/api.md
14
doc/api.md
@@ -30,6 +30,7 @@ Its constructor gets a number of options:
|
||||
* **onload**: (optional) handler for the iframe onload event.
|
||||
* **invitees**: (optional) Array of objects containing information about new participants that will be invited in the call.
|
||||
* **devices**: (optional) A map containing information about the initial devices that will be used in the call.
|
||||
* **userInfo**: (optional) JS object containing information about the participant opening the meeting, such as `email`.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -84,6 +85,19 @@ const options = {
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can set the userInfo(email) for the call:
|
||||
|
||||
```javascript
|
||||
var domain = "meet.jit.si";
|
||||
var options = {
|
||||
...
|
||||
userInfo: {
|
||||
email: 'email@jitsiexamplemail.com'
|
||||
}
|
||||
}
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
### Controlling the embedded Jitsi Meet Conference
|
||||
|
||||
Device management `JitsiMeetExternalAPI` methods:
|
||||
|
||||
@@ -45,7 +45,7 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
location ~ ^/([^?&:’“]+)$ {
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<!--#include virtual="head.html" -->
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -151,6 +152,7 @@
|
||||
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="body.html" -->
|
||||
<div id="react"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -274,7 +274,7 @@ PODS:
|
||||
- React
|
||||
- react-native-netinfo (4.1.5):
|
||||
- React
|
||||
- react-native-webrtc (1.75.0):
|
||||
- react-native-webrtc (1.75.2):
|
||||
- React
|
||||
- react-native-webview (7.4.1):
|
||||
- React
|
||||
@@ -534,7 +534,7 @@ SPEC CHECKSUMS:
|
||||
react-native-calendar-events: 2fe35a9294af05de0ed819d3a1b5dac048d2c010
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-webrtc: c5e3d631179a933548a8e49bddbd8fad02586095
|
||||
react-native-webrtc: f6783727706d8bec5fb302b76eda60c33dfe3191
|
||||
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
|
||||
React-RCTActionSheet: 94671eef55b01a93be735605822ef712d5ea208e
|
||||
React-RCTAnimation: 524ae33e73de9c0fe6501a7a4bda8e01d26499d9
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>19.4.0</string>
|
||||
<string>19.5.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>19.4.0</string>
|
||||
<string>19.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>19.4.0</string>
|
||||
<string>19.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -61,7 +61,21 @@ platform :ios do
|
||||
)
|
||||
|
||||
# Upload the build to TestFlight (but don't distribute it)
|
||||
upload_to_testflight(skip_submission: true, skip_waiting_for_build_processing: true)
|
||||
upload_to_testflight(
|
||||
beta_app_description: ENV["JITSI_CHANGELOG"],
|
||||
beta_app_feedback_email: ENV["JITSI_REVIEW_EMAIL"],
|
||||
beta_app_review_info: {
|
||||
contact_email: ENV["JITSI_REVIEW_EMAIL"],
|
||||
demo_account_name: ENV["JITSI_DEMO_ACCOUNT"],
|
||||
demo_account_password: ENV["JITSI_DEMO_PASSWORD"],
|
||||
},
|
||||
changelog: ENV["JITSI_CHANGELOG"],
|
||||
demo_account_required: false,
|
||||
distribute_external: true,
|
||||
groups: ENV["JITSI_BETA_TESTING_GROUPS"],
|
||||
reject_build_waiting_for_review: true,
|
||||
uses_non_exempt_encryption: false
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
clean_build_artifacts
|
||||
|
||||
@@ -25,7 +25,7 @@ popd
|
||||
# Build the SDK
|
||||
pushd ${PROJECT_REPO}
|
||||
rm -rf ios/sdk/JitsiMeet.framework
|
||||
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release archive
|
||||
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release ENABLE_BITCODE=NO clean archive
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.4.0</string>
|
||||
<string>2.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -225,7 +225,7 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
urlProps[@"jwt"] = _token;
|
||||
}
|
||||
|
||||
if (_token == nil && _userInfo != nil) {
|
||||
if (_userInfo != nil) {
|
||||
props[@"userInfo"] = [self.userInfo asDict];
|
||||
}
|
||||
|
||||
|
||||
8
modules/API/external/external_api.js
vendored
8
modules/API/external/external_api.js
vendored
@@ -232,6 +232,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* information about new participants that will be invited in the call.
|
||||
* @param {Array<Object>} [options.devices] - Array of objects containing
|
||||
* information about the initial devices that will be used in the call.
|
||||
* @param {Object} [options.userInfo] - Object containing information about
|
||||
* the participant opening the meeting.
|
||||
*/
|
||||
constructor(domain, ...args) {
|
||||
super();
|
||||
@@ -246,7 +248,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
jwt = undefined,
|
||||
onload = undefined,
|
||||
invitees,
|
||||
devices
|
||||
devices,
|
||||
userInfo
|
||||
} = parseArguments(args);
|
||||
|
||||
this._parentNode = parentNode;
|
||||
@@ -256,7 +259,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
jwt,
|
||||
noSSL,
|
||||
roomName,
|
||||
devices
|
||||
devices,
|
||||
userInfo
|
||||
});
|
||||
this._createIFrame(height, width, onload);
|
||||
this._transport = new Transport({
|
||||
|
||||
@@ -683,8 +683,8 @@ export default class LargeVideoManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the known resolution state of the
|
||||
* large video and adjusts container sizes when the resolution changes.
|
||||
* Dispatches an action to update the known resolution state of the large video and adjusts container sizes when the
|
||||
* resolution changes.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
@@ -697,7 +697,7 @@ export default class LargeVideoManager {
|
||||
APP.store.dispatch(updateKnownLargeVideoResolution(height));
|
||||
}
|
||||
|
||||
const currentAspectRatio = width / height;
|
||||
const currentAspectRatio = height === 0 ? 0 : width / height;
|
||||
|
||||
if (this._videoAspectRatio !== currentAspectRatio) {
|
||||
this._videoAspectRatio = currentAspectRatio;
|
||||
|
||||
@@ -5,10 +5,8 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { browser } from '../../../react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
ORIENTATION,
|
||||
LargeVideoBackground
|
||||
} from '../../../react/features/large-video';
|
||||
import { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
import Filmstrip from './Filmstrip';
|
||||
@@ -55,8 +53,12 @@ function computeDesktopVideoSize( // eslint-disable-line max-params
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight) {
|
||||
const aspectRatio = videoWidth / videoHeight;
|
||||
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
|
||||
// Avoid NaN values caused by devision by 0.
|
||||
return [ 0, 0 ];
|
||||
}
|
||||
|
||||
const aspectRatio = videoWidth / videoHeight;
|
||||
let availableWidth = Math.max(videoWidth, videoSpaceWidth);
|
||||
let availableHeight = Math.max(videoHeight, videoSpaceHeight);
|
||||
|
||||
@@ -99,6 +101,11 @@ function computeCameraVideoSize( // eslint-disable-line max-params
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight,
|
||||
videoLayoutFit) {
|
||||
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
|
||||
// Avoid NaN values caused by devision by 0.
|
||||
return [ 0, 0 ];
|
||||
}
|
||||
|
||||
const aspectRatio = videoWidth / videoHeight;
|
||||
|
||||
switch (videoLayoutFit) {
|
||||
@@ -322,7 +329,7 @@ export class VideoContainer extends LargeContainer {
|
||||
* @param {number} containerHeight container height
|
||||
* @returns {{availableWidth, availableHeight}}
|
||||
*/
|
||||
getVideoSize(containerWidth, containerHeight) {
|
||||
_getVideoSize(containerWidth, containerHeight) {
|
||||
const { width, height } = this.getStreamSize();
|
||||
|
||||
if (this.stream && this.isScreenSharing()) {
|
||||
@@ -414,13 +421,29 @@ export class VideoContainer extends LargeContainer {
|
||||
if (this.$video.length === 0) {
|
||||
return;
|
||||
}
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
|
||||
const [ width, height ]
|
||||
= this.getVideoSize(containerWidth, containerHeight);
|
||||
if (currentLayout === LAYOUTS.TILE_VIEW) {
|
||||
// We don't need to resize the large video since it won't be displayed and we'll resize when returning back
|
||||
// to stage view.
|
||||
return;
|
||||
}
|
||||
|
||||
this.positionRemoteStatusMessages();
|
||||
|
||||
const [ width, height ] = this._getVideoSize(containerWidth, containerHeight);
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
// We don't need to set 0 for width or height since the visibility is controled by the visibility css prop
|
||||
// on the largeVideoElementsContainer. Also if the width/height of the video element is 0 the attached
|
||||
// stream won't be played. Normally if we attach a new stream we won't resize the video element until the
|
||||
// stream has been played. But setting width/height to 0 will prevent the video from playing.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((containerWidth > width) || (containerHeight > height)) {
|
||||
this._backgroundOrientation = containerWidth > width
|
||||
? ORIENTATION.LANDSCAPE : ORIENTATION.PORTRAIT;
|
||||
this._backgroundOrientation = containerWidth > width ? ORIENTATION.LANDSCAPE : ORIENTATION.PORTRAIT;
|
||||
this._hideBackground = false;
|
||||
} else {
|
||||
this._hideBackground = true;
|
||||
@@ -429,15 +452,7 @@ export class VideoContainer extends LargeContainer {
|
||||
this._updateBackground();
|
||||
|
||||
const { horizontalIndent, verticalIndent }
|
||||
= this.getVideoPosition(width, height,
|
||||
containerWidth, containerHeight);
|
||||
|
||||
// update avatar position
|
||||
const top = (containerHeight / 2) - (this.avatarHeight / 4 * 3);
|
||||
|
||||
this.$avatar.css('top', top);
|
||||
|
||||
this.positionRemoteStatusMessages();
|
||||
= this.getVideoPosition(width, height, containerWidth, containerHeight);
|
||||
|
||||
this.$wrapper.animate({
|
||||
width,
|
||||
|
||||
@@ -22,7 +22,6 @@ import SharedVideoThumb from '../shared_video/SharedVideoThumb';
|
||||
|
||||
import Filmstrip from './Filmstrip';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import RemoteVideo from './RemoteVideo';
|
||||
import LargeVideoManager from './LargeVideoManager';
|
||||
@@ -663,14 +662,6 @@ const VideoLayout = {
|
||||
largeVideo.updateContainerSize();
|
||||
largeVideo.resize(animate);
|
||||
}
|
||||
|
||||
// Calculate available width and height.
|
||||
const availableHeight = window.innerHeight;
|
||||
const availableWidth = UIUtil.getAvailableVideoWidth();
|
||||
|
||||
if (availableWidth < 0 || availableHeight < 0) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
getSmallVideo(id) {
|
||||
|
||||
@@ -9,43 +9,6 @@ const DEFAULT_POSTIS_OPTIONS = {
|
||||
window: window.opener || window.parent
|
||||
};
|
||||
|
||||
/**
|
||||
* The list of methods of incoming postis messages that we have to support for
|
||||
* backward compatibility for the users that are directly sending messages to
|
||||
* Jitsi Meet (without using external_api.js)
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const LEGACY_INCOMING_METHODS = [
|
||||
'avatar-url',
|
||||
'display-name',
|
||||
'email',
|
||||
'toggle-audio',
|
||||
'toggle-chat',
|
||||
'toggle-film-strip',
|
||||
'toggle-share-screen',
|
||||
'toggle-video',
|
||||
'video-hangup'
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of methods of outgoing postis messages that we have to support for
|
||||
* backward compatibility for the users that are directly listening to the
|
||||
* postis messages send by Jitsi Meet(without using external_api.js).
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const LEGACY_OUTGOING_METHODS = [
|
||||
'display-name-change',
|
||||
'incoming-message',
|
||||
'outgoing-message',
|
||||
'participant-joined',
|
||||
'participant-left',
|
||||
'video-conference-joined',
|
||||
'video-conference-left',
|
||||
'video-ready-to-close'
|
||||
];
|
||||
|
||||
/**
|
||||
* The postis method used for all messages.
|
||||
*
|
||||
@@ -63,34 +26,13 @@ export default class PostMessageTransportBackend {
|
||||
* @param {Object} options - Optional parameters for configuration of the
|
||||
* transport.
|
||||
*/
|
||||
constructor({ enableLegacyFormat, postisOptions } = {}) {
|
||||
constructor({ postisOptions } = {}) {
|
||||
// eslint-disable-next-line new-cap
|
||||
this.postis = Postis({
|
||||
...DEFAULT_POSTIS_OPTIONS,
|
||||
...postisOptions
|
||||
});
|
||||
|
||||
/**
|
||||
* If true PostMessageTransportBackend will process and send messages
|
||||
* using the legacy format and in the same time the current format.
|
||||
* Otherwise all messages (outgoing and incoming) that are using the
|
||||
* legacy format will be ignored.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
this._enableLegacyFormat = enableLegacyFormat;
|
||||
|
||||
if (this._enableLegacyFormat) {
|
||||
// backward compatibility
|
||||
LEGACY_INCOMING_METHODS.forEach(method =>
|
||||
this.postis.listen(
|
||||
method,
|
||||
params =>
|
||||
this._legacyMessageReceivedCallback(method, params)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._receiveCallback = () => {
|
||||
// Do nothing until a callback is set by the consumer of
|
||||
// PostMessageTransportBackend via setReceiveCallback.
|
||||
@@ -101,37 +43,6 @@ export default class PostMessageTransportBackend {
|
||||
message => this._receiveCallback(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming legacy postis messages.
|
||||
*
|
||||
* @param {string} method - The method property from the postis message.
|
||||
* @param {Any} params - The params property from the postis message.
|
||||
* @returns {void}
|
||||
*/
|
||||
_legacyMessageReceivedCallback(method, params = {}) {
|
||||
this._receiveCallback({
|
||||
data: {
|
||||
name: method,
|
||||
data: params
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the passed message via postis using the old format.
|
||||
*
|
||||
* @param {Object} legacyMessage - The message to be sent.
|
||||
* @returns {void}
|
||||
*/
|
||||
_sendLegacyMessage({ name, ...data }) {
|
||||
if (name && LEGACY_OUTGOING_METHODS.indexOf(name) !== -1) {
|
||||
this.postis.send({
|
||||
method: name,
|
||||
params: data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
@@ -152,14 +63,6 @@ export default class PostMessageTransportBackend {
|
||||
method: POSTIS_METHOD_NAME,
|
||||
params: message
|
||||
});
|
||||
|
||||
if (this._enableLegacyFormat) {
|
||||
// For the legacy use case we don't need any new fields defined in
|
||||
// Transport class. That's why we are passing only the original
|
||||
// object passed by the consumer of the Transport class which is
|
||||
// message.data.
|
||||
this._sendLegacyMessage(message.data || {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,12 +36,7 @@ let transport;
|
||||
*/
|
||||
export function getJitsiMeetTransport() {
|
||||
if (!transport) {
|
||||
transport = new Transport({
|
||||
backend: new PostMessageTransportBackend({
|
||||
enableLegacyFormat: true,
|
||||
postisOptions
|
||||
})
|
||||
});
|
||||
transport = new Transport({ backend: new PostMessageTransportBackend({ postisOptions }) });
|
||||
}
|
||||
|
||||
return transport;
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -10931,8 +10931,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
|
||||
"from": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
|
||||
"version": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
|
||||
"from": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.14",
|
||||
"@jitsi/sdp-simulcast": "0.2.2",
|
||||
@@ -14887,8 +14887,9 @@
|
||||
"integrity": "sha512-l3Quzbb+qa4in2U5RSt/lT0/pHrIpEChT1NnqrVAAXNrjkXjVOsxduaaEDdDhTzNJQEm/PcAcoyrFmgvGOohxw=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"from": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"version": "1.75.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.75.2.tgz",
|
||||
"integrity": "sha512-mdEukmHNhiyVIiwdooxk4kVXWG83OOENFV9YIkC7dtGU/sOdL81vDzynqd6Af9YbGMeOr0xdpFuEGsc1OFnKZg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"event-target-shim": "^1.0.5",
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
@@ -80,7 +80,7 @@
|
||||
"react-native-svg-transformer": "0.13.0",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.2.0",
|
||||
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"react-native-webrtc": "1.75.2",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
|
||||
@@ -9,10 +9,7 @@ import { DialogContainer } from '../../base/dialog';
|
||||
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import {
|
||||
AspectRatioDetector,
|
||||
ReducedUIDetector
|
||||
} from '../../base/responsive-ui';
|
||||
import '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import '../../google-api';
|
||||
import '../../mobile/audio-mode';
|
||||
@@ -110,22 +107,6 @@ export class App extends AbstractApp {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects {@link AspectRatioDetector} in order to detect the aspect ratio
|
||||
* of this {@code App}'s user interface and afford {@link AspectRatioAware}.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createMainElement(component, props) {
|
||||
return (
|
||||
<AspectRatioDetector>
|
||||
<ReducedUIDetector>
|
||||
{ super._createMainElement(component, props) }
|
||||
</ReducedUIDetector>
|
||||
</AspectRatioDetector>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disable the use of React Native
|
||||
* {@link ExceptionsManager#handleException} on platforms and in
|
||||
|
||||
@@ -5,7 +5,6 @@ import React from 'react';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/user-interaction';
|
||||
import '../../base/responsive-ui';
|
||||
import '../../chat';
|
||||
import '../../external-api';
|
||||
import '../../power-monitor';
|
||||
|
||||
@@ -14,6 +14,7 @@ import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setVideoMuted } from '../media';
|
||||
import {
|
||||
dominantSpeakerChanged,
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
participantConnectionStatusChanged,
|
||||
participantKicked,
|
||||
@@ -393,14 +394,18 @@ export function createConference() {
|
||||
throw new Error('Cannot join a conference without a room name!');
|
||||
}
|
||||
|
||||
const config = state['features/base/config'];
|
||||
const { email, name: nick } = getLocalParticipant(state);
|
||||
const conference
|
||||
= connection.initJitsiConference(
|
||||
|
||||
getBackendSafeRoomName(room), {
|
||||
...state['features/base/config'],
|
||||
...config,
|
||||
applicationName: getName(),
|
||||
getWiFiStatsMethod: getJitsiMeetGlobalNS().getWiFiStats,
|
||||
confID: `${locationURL.host}${locationURL.pathname}`
|
||||
confID: `${locationURL.host}${locationURL.pathname}`,
|
||||
statisticsDisplayName: config.enableDisplayNameInStats ? nick : undefined,
|
||||
statisticsId: config.enableEmailInStats ? email : undefined
|
||||
});
|
||||
|
||||
connection[JITSI_CONNECTION_CONFERENCE_KEY] = conference;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
participantLeft
|
||||
} from '../participants';
|
||||
import { toState } from '../redux';
|
||||
import { safeDecodeURIComponent } from '../util';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
@@ -163,7 +164,7 @@ export function getConferenceName(stateful: Function | Object): string {
|
||||
|| subject
|
||||
|| callDisplayName
|
||||
|| (callee && callee.name)
|
||||
|| _.startCase(decodeURIComponent(room));
|
||||
|| _.startCase(safeDecodeURIComponent(room));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,13 +178,15 @@ export function getConferenceName(stateful: Function | Object): string {
|
||||
* @returns {JitsiConference|undefined}
|
||||
*/
|
||||
export function getCurrentConference(stateful: Function | Object) {
|
||||
const { conference, joining, leaving }
|
||||
const { conference, joining, leaving, passwordRequired }
|
||||
= toState(stateful)['features/base/conference'];
|
||||
|
||||
return (
|
||||
conference
|
||||
? conference === leaving ? undefined : conference
|
||||
: joining);
|
||||
// There is a precendence
|
||||
if (conference) {
|
||||
return conference === leaving ? undefined : conference;
|
||||
}
|
||||
|
||||
return joining || passwordRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,10 +15,6 @@ export default [
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
'callFlowsEnabled',
|
||||
'callStatsConfIDNamespace',
|
||||
'callStatsID',
|
||||
'callStatsSecret',
|
||||
|
||||
/**
|
||||
* The display name of the CallKit call representing the conference/meeting
|
||||
@@ -34,6 +30,7 @@ export default [
|
||||
* @type string
|
||||
*/
|
||||
'callDisplayName',
|
||||
'callFlowsEnabled',
|
||||
|
||||
/**
|
||||
* The handle
|
||||
@@ -48,6 +45,9 @@ export default [
|
||||
* @type string
|
||||
*/
|
||||
'callHandle',
|
||||
'callStatsConfIDNamespace',
|
||||
'callStatsID',
|
||||
'callStatsSecret',
|
||||
|
||||
/**
|
||||
* The UUID of the CallKit call representing the conference/meeting
|
||||
@@ -73,8 +73,8 @@ export default [
|
||||
'desktopSharingChromeExtId',
|
||||
'desktopSharingChromeMinExtVersion',
|
||||
'desktopSharingChromeSources',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingFirefoxDisabled',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingSources',
|
||||
'disable1On1Mode',
|
||||
'disableAEC',
|
||||
@@ -84,6 +84,7 @@ export default [
|
||||
'disableDeepLinking',
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableLocalVideoFlip',
|
||||
'disableNS',
|
||||
'disableRemoteControl',
|
||||
'disableRtx',
|
||||
@@ -91,11 +92,10 @@ export default [
|
||||
'displayJids',
|
||||
'e2eping',
|
||||
'enableDisplayNameInStats',
|
||||
'enableEmailInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'disableLocalVideoFlip',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
'enableTcc',
|
||||
'etherpad_base',
|
||||
@@ -123,11 +123,12 @@ export default [
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startBitrate',
|
||||
'startSilent',
|
||||
'startScreenSharing',
|
||||
'startSilent',
|
||||
'startVideoMuted',
|
||||
'startWithAudioMuted',
|
||||
'startWithVideoMuted',
|
||||
'stereo',
|
||||
'subject',
|
||||
'testing',
|
||||
'useIPv6',
|
||||
|
||||
@@ -22,6 +22,7 @@ export default [
|
||||
'DEFAULT_REMOTE_DISPLAY_NAME',
|
||||
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
|
||||
'DISABLE_FOCUS_INDICATOR',
|
||||
'DISABLE_PRIVATE_MESSAGES',
|
||||
'DISABLE_RINGING',
|
||||
'DISABLE_TRANSCRIPTION_SUBTITLES',
|
||||
'DISABLE_VIDEO_BACKGROUND',
|
||||
|
||||
@@ -12,9 +12,7 @@ import type { Dispatch } from 'redux';
|
||||
* very brittle because it's completely disconnected from the UI which wants to
|
||||
* be rendered and, naturally, it broke on iPad where even the secondary Toolbar
|
||||
* didn't fit in the height. We do need to measure the actual UI at runtime and
|
||||
* determine whether and how to render it. I'm bumping from 240 to 300 because I
|
||||
* don't have the time now to refactor {@code ReducedUIDetector} or rip it out
|
||||
* completely.
|
||||
* determine whether and how to render it.
|
||||
*/
|
||||
const REDUCED_UI_THRESHOLD = 300;
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component, type Node } from 'react';
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { connect } from '../../redux';
|
||||
|
||||
import { setAspectRatio } from '../actions';
|
||||
import DimensionsDetector from './DimensionsDetector';
|
||||
|
||||
/**
|
||||
* AspectRatioDetector component's property types.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The "onDimensionsHandler" handler.
|
||||
*/
|
||||
_onDimensionsChanged: Function,
|
||||
|
||||
/**
|
||||
* Any nested components.
|
||||
*/
|
||||
children: Node
|
||||
};
|
||||
|
||||
/**
|
||||
* A root {@link View} which captures the 'onLayout' event and figures out
|
||||
* the aspect ratio of the app.
|
||||
*/
|
||||
class AspectRatioDetector extends Component<Props> {
|
||||
/**
|
||||
* Renders the root view and it's children.
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this.props._onDimensionsChanged } >
|
||||
{ this.props.children }
|
||||
</DimensionsDetector>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps dispatching of the aspect ratio actions to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onDimensionsChanged: Function
|
||||
* }}
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Dispatch<any>) {
|
||||
return {
|
||||
/**
|
||||
* Handles the "on dimensions changed" event and dispatches aspect ratio
|
||||
* changed action.
|
||||
*
|
||||
* @param {number} width - The new width for the component.
|
||||
* @param {number} height - The new height for the component.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDimensionsChanged(width: number, height: number) {
|
||||
dispatch(setAspectRatio(width, height));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(undefined, _mapDispatchToProps)(AspectRatioDetector);
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
@@ -25,7 +25,7 @@ type Props = {
|
||||
* A {@link View} which captures the 'onLayout' event and calls a prop with the
|
||||
* component size.
|
||||
*/
|
||||
export default class DimensionsDetector extends Component<Props> {
|
||||
export default class DimensionsDetector extends PureComponent<Props> {
|
||||
/**
|
||||
* Initializes a new DimensionsDetector instance.
|
||||
*
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component, type Node } from 'react';
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { connect } from '../../redux';
|
||||
|
||||
import { setReducedUI } from '../actions';
|
||||
import DimensionsDetector from './DimensionsDetector';
|
||||
|
||||
|
||||
/**
|
||||
* ReducedUIDetector component's property types.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The "onDimensionsHandler" handler.
|
||||
*/
|
||||
_onDimensionsChanged: Function,
|
||||
|
||||
/**
|
||||
* Any nested components.
|
||||
*/
|
||||
children: Node
|
||||
};
|
||||
|
||||
/**
|
||||
* A root {@link View} which captures the 'onLayout' event and figures out
|
||||
* if the UI is reduced.
|
||||
*/
|
||||
class ReducedUIDetector extends Component<Props> {
|
||||
/**
|
||||
* Renders the root view and it's children.
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this.props._onDimensionsChanged } >
|
||||
{ this.props.children }
|
||||
</DimensionsDetector>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps dispatching of the reduced UI actions to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onDimensionsChanged: Function
|
||||
* }}
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Dispatch<any>) {
|
||||
return {
|
||||
/**
|
||||
* Handles the "on dimensions changed" event and dispatches the
|
||||
* reduced UI action.
|
||||
*
|
||||
* @param {number} width - The new width for the component.
|
||||
* @param {number} height - The new height for the component.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDimensionsChanged(width: number, height: number) {
|
||||
dispatch(setReducedUI(width, height));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(undefined, _mapDispatchToProps)(ReducedUIDetector);
|
||||
@@ -1,4 +1,2 @@
|
||||
export * from './AspectRatioAware';
|
||||
export { default as AspectRatioDetector } from './AspectRatioDetector';
|
||||
export { default as DimensionsDetector } from './DimensionsDetector';
|
||||
export { default as ReducedUIDetector } from './ReducedUIDetector';
|
||||
|
||||
@@ -3,4 +3,5 @@ export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
83
react/features/base/responsive-ui/middleware.native.js
Normal file
83
react/features/base/responsive-ui/middleware.native.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// @flow
|
||||
|
||||
import { Dimensions } from 'react-native';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
|
||||
import { setAspectRatio, setReducedUI } from './actions';
|
||||
|
||||
/**
|
||||
* Dimensions change handler.
|
||||
*/
|
||||
let handler;
|
||||
|
||||
/**
|
||||
* Middleware that handles widnow dimension changes and updates the aspect ratio and
|
||||
* reduced UI modes accordingly.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_UNMOUNT: {
|
||||
_appWillUnmount();
|
||||
break;
|
||||
}
|
||||
case APP_WILL_MOUNT:
|
||||
_appWillMount(store);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appWillMount(store) {
|
||||
handler = dim => {
|
||||
_onDimensionsChange(dim, store);
|
||||
};
|
||||
|
||||
Dimensions.addEventListener('change', handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appWillUnmount() {
|
||||
Dimensions.removeEventListener('change', handler);
|
||||
|
||||
handler = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles window dimension changes.
|
||||
*
|
||||
* @param {Object} dimensions - The new dimensions.
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onDimensionsChange(dimensions, store) {
|
||||
const { width, height } = dimensions.window;
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(setAspectRatio(width, height));
|
||||
dispatch(setReducedUI(width, height));
|
||||
}
|
||||
0
react/features/base/responsive-ui/middleware.web.js
Normal file
0
react/features/base/responsive-ui/middleware.web.js
Normal file
@@ -13,16 +13,14 @@ const DEFAULT_STATE = {
|
||||
reducedUI: false
|
||||
};
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/base/responsive-ui',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_ASPECT_RATIO:
|
||||
return set(state, 'aspectRatio', action.aspectRatio);
|
||||
ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_ASPECT_RATIO:
|
||||
return set(state, 'aspectRatio', action.aspectRatio);
|
||||
|
||||
case SET_REDUCED_UI:
|
||||
return set(state, 'reducedUI', action.reducedUI);
|
||||
}
|
||||
case SET_REDUCED_UI:
|
||||
return set(state, 'reducedUI', action.reducedUI);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
return state;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// @flow
|
||||
import _ from 'lodash';
|
||||
|
||||
import { APP_WILL_MOUNT } from '../app';
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import parseURLParams from '../config/parseURLParams'; // minimize imports to avoid circular imports
|
||||
import { SET_LOCATION_URL } from '../connection/actionTypes'; // minimize imports to avoid circular imports
|
||||
import { getLocalParticipant, participantUpdated } from '../participants';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
@@ -19,15 +23,37 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
_initializeCallIntegration(store);
|
||||
break;
|
||||
case SETTINGS_UPDATED:
|
||||
_maybeHandleCallIntegrationChange(action);
|
||||
_maybeSetAudioOnly(store, action);
|
||||
_updateLocalParticipant(store, action);
|
||||
break;
|
||||
case SET_LOCATION_URL:
|
||||
_updateLocalParticipantFromUrl(store);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the audio device handler based on the `disableCallIntegration` setting.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _initializeCallIntegration({ getState }) {
|
||||
const { disableCallIntegration } = getState()['features/base/settings'];
|
||||
|
||||
if (typeof disableCallIntegration === 'boolean') {
|
||||
handleCallIntegrationChange(disableCallIntegration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the settings field names to participant names where they don't match.
|
||||
* Currently there is only one such field, but may be extended in the future.
|
||||
@@ -46,7 +72,7 @@ function _mapSettingsFieldToParticipant(settingsField) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates {@code startAudioOnly} flag if it's updated in the settings.
|
||||
* Handles a change in the `disableCallIntegration` setting.
|
||||
*
|
||||
* @param {Object} action - The redux action.
|
||||
* @private
|
||||
@@ -98,3 +124,30 @@ function _updateLocalParticipant({ dispatch, getState }, action) {
|
||||
|
||||
dispatch(participantUpdated(newLocalParticipant));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the userInfo set in the URL.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _updateLocalParticipantFromUrl({ dispatch, getState }) {
|
||||
const urlParams
|
||||
= parseURLParams(getState()['features/base/connection'].locationURL);
|
||||
const urlEmail = urlParams['userInfo.email'];
|
||||
|
||||
if (!urlEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localParticipant = getLocalParticipant(getState());
|
||||
|
||||
if (localParticipant) {
|
||||
dispatch(participantUpdated({
|
||||
...localParticipant,
|
||||
email: _.escape(urlEmail)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import md5 from 'js-md5';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
declare var __DEV__;
|
||||
|
||||
/**
|
||||
* The name of the {@code localStorage} store where the app persists its values.
|
||||
*/
|
||||
@@ -87,7 +89,9 @@ class PersistenceRegistry {
|
||||
// Initialize the checksum.
|
||||
this._checksum = this._calculateChecksum(filteredPersistedState);
|
||||
|
||||
logger.info('redux state rehydrated as', filteredPersistedState);
|
||||
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
||||
logger.info('redux state rehydrated as', filteredPersistedState);
|
||||
}
|
||||
|
||||
return filteredPersistedState;
|
||||
}
|
||||
@@ -113,7 +117,6 @@ class PersistenceRegistry {
|
||||
logger.error(
|
||||
'Error persisting redux subtree',
|
||||
subtreeName,
|
||||
filteredState[subtreeName],
|
||||
error);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +156,7 @@ class PersistenceRegistry {
|
||||
try {
|
||||
return md5.hex(JSON.stringify(state) || '');
|
||||
} catch (error) {
|
||||
logger.error('Error calculating checksum for state', state, error);
|
||||
logger.error('Error calculating checksum for state', error);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -373,6 +373,23 @@ function _standardURIToString(thiz: ?Object) {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes we receive strings that we don't know if already percent-encoded, or not, due to the
|
||||
* various sources we get URLs or room names. This function encapsulates the decoding in a safe way.
|
||||
*
|
||||
* @param {string} text - The text to decode.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function safeDecodeURIComponent(text: string) {
|
||||
try {
|
||||
return decodeURIComponent(text);
|
||||
} catch (e) {
|
||||
// The text wasn't encoded.
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to return a {@code String} representation of a specific
|
||||
* {@code Object} which is supposed to represent a URL. Obviously, if a
|
||||
@@ -510,7 +527,7 @@ export function urlObjectToString(o: Object): ?string {
|
||||
|
||||
let { hash } = url;
|
||||
|
||||
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices' ]) {
|
||||
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices', 'userInfo' ]) {
|
||||
const urlParamsArray
|
||||
= _objectToURLParamsArray(
|
||||
o[`${urlPrefix}Overwrite`]
|
||||
|
||||
@@ -20,7 +20,7 @@ export function notifyKickedOut(participant: Object, _: ?Function) { // eslint-d
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const args = {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getDisplayName())
|
||||
getParticipantDisplayName(getState, participant.getId())
|
||||
};
|
||||
|
||||
dispatch(showNotification({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
import { NativeModules, SafeAreaView, StatusBar } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import { appNavigate } from '../../../app';
|
||||
@@ -279,7 +279,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
</TintedView>
|
||||
}
|
||||
|
||||
<View
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolboxAndFilmstripContainer }>
|
||||
|
||||
@@ -320,7 +320,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
_shouldDisplayTileView ? undefined : <Filmstrip />
|
||||
}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
|
||||
@@ -94,6 +94,23 @@ class DeepLinkingMobilePage extends Component<Props> {
|
||||
const downloadButtonClassName
|
||||
= `${_SNS}__button ${_SNS}__button_primary`;
|
||||
|
||||
|
||||
const onOpenLinkProperties = _URLS[Platform.OS]
|
||||
? {
|
||||
// When opening a link to the download page, we want to let the
|
||||
// OS itself handle intercepting and opening the appropriate
|
||||
// app store. This avoids potential issues with browsers, such
|
||||
// as iOS Chrome, not opening the store properly.
|
||||
}
|
||||
: {
|
||||
// When falling back to another URL (Firebase) let the page be
|
||||
// opened in a new window. This helps prevent the user getting
|
||||
// trapped in an app-open-cycle where going back to the mobile
|
||||
// browser re-triggers the app-open behavior.
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className = { _SNS }>
|
||||
<div className = 'header'>
|
||||
@@ -113,20 +130,18 @@ class DeepLinkingMobilePage extends Component<Props> {
|
||||
{ t(`${_TNS}.appNotInstalled`, { app: NATIVE_APP_NAME }) }
|
||||
</p>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
href = { this._generateDownloadURL() }
|
||||
onClick = { this._onDownloadApp }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
onClick = { this._onDownloadApp }>
|
||||
<button className = { downloadButtonClassName }>
|
||||
{ t(`${_TNS}.downloadApp`) }
|
||||
</button>
|
||||
</a>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { `${_SNS}__href` }
|
||||
href = { generateDeepLinkingURL() }
|
||||
onClick = { this._onOpenApp }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
onClick = { this._onOpenApp }>
|
||||
{/* <button className = { `${_SNS}__button` }> */}
|
||||
{ t(`${_TNS}.openApp`) }
|
||||
{/* </button> */}
|
||||
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_FAILED,
|
||||
JITSI_CONNECTION_CONFERENCE_KEY,
|
||||
JITSI_CONNECTION_URL_KEY
|
||||
JITSI_CONNECTION_URL_KEY,
|
||||
getURLWithoutParams
|
||||
} from '../../base/connection';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { toURLString } from '../../base/util';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
|
||||
|
||||
import { sendEvent } from './functions';
|
||||
@@ -82,7 +82,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
store,
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
url: toURLString(locationURL)
|
||||
url: _normalizeUrl(locationURL)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
error: _toErrorString(error),
|
||||
url: toURLString(locationURL)
|
||||
url: _normalizeUrl(locationURL)
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -161,10 +161,20 @@ function _maybeTriggerEarlyConferenceWillJoin(store, action) {
|
||||
store,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
/* data */ {
|
||||
url: toURLString(locationURL)
|
||||
url: _normalizeUrl(locationURL)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given URL for presentation over the external API.
|
||||
*
|
||||
* @param {URL} url -The URL to normalize.
|
||||
* @returns {string} - The normalized URL as a string.
|
||||
*/
|
||||
function _normalizeUrl(url: URL) {
|
||||
return getURLWithoutParams(url).href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an event to the native counterpart of the External API for a specific
|
||||
* conference-related redux action.
|
||||
@@ -186,7 +196,7 @@ function _sendConferenceEvent(
|
||||
// instance. The external API cannot transport such an object so we have to
|
||||
// transport an "equivalent".
|
||||
if (conference) {
|
||||
data.url = toURLString(conference[JITSI_CONFERENCE_URL_KEY]);
|
||||
data.url = _normalizeUrl(conference[JITSI_CONFERENCE_URL_KEY]);
|
||||
}
|
||||
|
||||
if (_swallowEvent(store, action, data)) {
|
||||
@@ -233,7 +243,7 @@ function _sendConferenceFailedOnConnectionError(store, action) {
|
||||
store,
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
url: toURLString(locationURL),
|
||||
url: _normalizeUrl(locationURL),
|
||||
error: action.error.name
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getLocalizedDurationFormatter
|
||||
} from '../base/i18n';
|
||||
import { NavigateSectionList } from '../base/react';
|
||||
import { parseURIString } from '../base/util';
|
||||
import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
|
||||
/**
|
||||
* Creates a displayable list item of a recent list entry.
|
||||
@@ -31,7 +31,7 @@ function toDisplayableItem(item, defaultServerURL, t) {
|
||||
_toDurationString(item.duration),
|
||||
serverName
|
||||
],
|
||||
title: decodeURIComponent(location.room),
|
||||
title: safeDecodeURIComponent(location.room),
|
||||
url: item.conference
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,10 +5,25 @@ import React, { Component } from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMessage } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { _mapDispatchToProps, _mapStateToProps, type Props } from '../../../chat/components/PrivateMessageButton';
|
||||
import {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../../../chat/components/PrivateMessageButton';
|
||||
import { isButtonEnabled } from '../../../toolbox';
|
||||
|
||||
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* True if the private chat functionality is disabled, hence the button is not visible.
|
||||
*/
|
||||
_hidden: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom implementation of the PrivateMessageButton specialized for
|
||||
* the web version of the remote video menu. When the web platform starts to use
|
||||
@@ -34,7 +49,11 @@ class PrivateMessageMenuButton extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { participantID, t } = this.props;
|
||||
const { participantID, t, _hidden } = this.props;
|
||||
|
||||
if (_hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
@@ -59,4 +78,19 @@ class PrivateMessageMenuButton extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
|
||||
return {
|
||||
..._abstractMapStateToProps(state, ownProps),
|
||||
_hidden: typeof interfaceConfig !== 'undefined'
|
||||
&& (interfaceConfig.DISABLE_PRIVATE_MESSAGES || !isButtonEnabled('chat'))
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(PrivateMessageMenuButton));
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Text, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import styles, { ANDROID_UNDERLINE_COLOR } from './styles';
|
||||
import styles, { ANDROID_UNDERLINE_COLOR, PLACEHOLDER_COLOR } from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link FormRow}
|
||||
@@ -107,6 +107,7 @@ class FormRow extends Component<Props> {
|
||||
switch (field.type.displayName) {
|
||||
case 'TextInput':
|
||||
return {
|
||||
placeholderTextColor: PLACEHOLDER_COLOR,
|
||||
style: styles.textInputField,
|
||||
underlineColorAndroid: ANDROID_UNDERLINE_COLOR
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
} from '../../../base/styles';
|
||||
|
||||
export const ANDROID_UNDERLINE_COLOR = 'transparent';
|
||||
export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
|
||||
|
||||
const TEXT_SIZE = 17;
|
||||
|
||||
/**
|
||||
@@ -79,6 +81,7 @@ export default createStyleSheet({
|
||||
* Standard text input field style.
|
||||
*/
|
||||
textInputField: {
|
||||
color: ColorPalette.black,
|
||||
flex: 1,
|
||||
fontSize: TEXT_SIZE,
|
||||
textAlign: 'right'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
@@ -46,37 +46,10 @@ type Props = {
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of {@link Toolbox}'s React {@code Component} state.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The detected width for this component.
|
||||
*/
|
||||
width: number
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the conference toolbox on React Native.
|
||||
*/
|
||||
class Toolbox extends Component<Props, State> {
|
||||
state = {
|
||||
width: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Toolbox} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onLayout = this._onLayout.bind(this);
|
||||
}
|
||||
|
||||
class Toolbox extends PureComponent<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -86,7 +59,6 @@ class Toolbox extends Component<Props, State> {
|
||||
render() {
|
||||
return (
|
||||
<Container
|
||||
onLayout = { this._onLayout }
|
||||
style = { styles.toolbox }
|
||||
visible = { this.props._visible }>
|
||||
{ this._renderToolbar() }
|
||||
@@ -125,20 +97,6 @@ class Toolbox extends Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
_onLayout: (Object) => void;
|
||||
|
||||
/**
|
||||
* Handles the "on layout" View's event and stores the width as state.
|
||||
*
|
||||
* @param {Object} event - The "on layout" event object/structure passed
|
||||
* by react-native.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLayout({ nativeEvent: { layout: { width } } }) {
|
||||
this.setState({ width });
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the toolbar. In order to avoid a weird visual effect in which the
|
||||
* toolbar is (visually) rendered and then visibly changes its size, it is
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local get_room_from_jid = module:require "util".get_room_from_jid;
|
||||
local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
|
||||
local jid_resource = require "util.jid".resource;
|
||||
local ext_events = module:require "ext_events"
|
||||
local st = require "util.stanza";
|
||||
@@ -25,7 +26,7 @@ function on_message(event)
|
||||
= event.stanza:get_child('speakerstats', 'http://jitsi.org/jitmeet');
|
||||
if speakerStats then
|
||||
local roomAddress = speakerStats.attr.room;
|
||||
local room = get_room_from_jid(roomAddress);
|
||||
local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
|
||||
|
||||
if not room then
|
||||
log("warn", "No room found %s", roomAddress);
|
||||
|
||||
Reference in New Issue
Block a user